Skip to content

Commit 3f23d80

Browse files
committed
refactor(cli): simplify dispose check in ArtifactRuntimeController
First, update the reload test to properly validate dispose and DOM cleanup behavior, then add gallery images and a carousel slider to the Artemis II lunar flyby block with auto-rotation and pause on interaction.
1 parent 9bf4e25 commit 3f23d80

4 files changed

Lines changed: 123 additions & 15 deletions

File tree

agent-html/artifacts/nasa-artemis-ii/data.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,48 @@ export const mediaAssets = {
312312
sourceUrl: "https://www.nasa.gov/image-detail/amf-art002e009301/",
313313
src: "https://images-assets.nasa.gov/image/art002e009301/art002e009301~large.jpg?crop=faces%2Cfocalpoint&fit=clip&h=1280&w=1920",
314314
},
315+
gallery: [
316+
{
317+
alt: "Earth setting behind the Moon's horizon during the Artemis II lunar flyby.",
318+
caption:
319+
"Earthset through Orion's window makes the Moon a foreground landmark.",
320+
credit: "Credit: NASA",
321+
sourceUrl: "https://www.nasa.gov/image-detail/art002e009288/",
322+
src: "https://images-assets.nasa.gov/image/art002e009288/art002e009288~large.jpg?crop=faces%2Cfocalpoint&fit=clip&h=1280&w=1920",
323+
},
324+
{
325+
alt: "The Moon backlit by the Sun during an in-space solar eclipse, with Orion visible in the foreground.",
326+
caption:
327+
"A solar eclipse seen from Orion turns the flyby into a spacecraft-scale scene.",
328+
credit: "Credit: NASA",
329+
sourceUrl: "https://www.nasa.gov/image-detail/art002e009573/",
330+
src: "https://images-assets.nasa.gov/image/art002e009573/art002e009573~large.jpg?crop=faces%2Cfocalpoint&fit=clip&h=1280&w=1920",
331+
},
332+
{
333+
alt: "The Moon and Earth captured in one frame during the Artemis II lunar flyby.",
334+
caption:
335+
"Moon and Earth share one frame as Orion crosses lunar space.",
336+
credit: "Credit: NASA",
337+
sourceUrl: "https://www.nasa.gov/gallery/lunar-flyby/",
338+
src: "https://images-assets.nasa.gov/image/art002e009567/art002e009567~large.jpg?crop=faces%2Cfocalpoint&fit=clip&h=1280&w=1920",
339+
},
340+
{
341+
alt: "A detailed lunar surface view with a distant Earth setting in the background.",
342+
caption:
343+
"Cratered terrain and distant Earth compress the scale of the flyby.",
344+
credit: "Credit: NASA",
345+
sourceUrl: "https://www.nasa.gov/image-detail/art002e009289/",
346+
src: "https://images-assets.nasa.gov/image/art002e009289/art002e009289~large.jpg?crop=faces%2Cfocalpoint&fit=clip&h=1280&w=1920",
347+
},
348+
{
349+
alt: "The Moon peeking above the Orion spacecraft window sill during Artemis II.",
350+
caption:
351+
"The Orion window frame keeps the lunar pass tied to the crew's viewpoint.",
352+
credit: "Credit: NASA",
353+
sourceUrl: "https://www.nasa.gov/gallery/lunar-flyby/",
354+
src: "https://images-assets.nasa.gov/image/art002e012278/art002e012278~large.jpg?crop=faces%2Cfocalpoint&fit=clip&h=1280&w=1920",
355+
},
356+
],
315357
},
316358
opening: {
317359
alt: "Earth seen from Orion during Artemis II.",

agent-html/artifacts/nasa-artemis-ii/lunar-flyby.block.tsx

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
1+
import { useEffect, useState } from "react"
2+
13
import { Badge } from "../../components/ui/badge"
4+
import {
5+
Carousel,
6+
type CarouselApi,
7+
CarouselContent,
8+
CarouselItem,
9+
} from "../../components/ui/carousel"
210

311
import { lunarMediaBeats, mediaAssets } from "./data"
412

513
export function LunarFlybyBlock() {
14+
const gallery = mediaAssets.lunarFlyby.gallery
15+
const [carouselApi, setCarouselApi] = useState<CarouselApi>()
16+
const [isCarouselPaused, setIsCarouselPaused] = useState(false)
17+
18+
useEffect(() => {
19+
if (!carouselApi || isCarouselPaused || gallery.length < 2) return
20+
21+
const intervalId = window.setInterval(() => {
22+
carouselApi.scrollNext()
23+
}, 4000)
24+
25+
return () => window.clearInterval(intervalId)
26+
}, [carouselApi, gallery.length, isCarouselPaused])
27+
628
return (
729
<section className="canvas-stack-lg">
830
<div className="canvas-stack-md">
@@ -20,17 +42,39 @@ export function LunarFlybyBlock() {
2042
the Earth-Moon relationship.
2143
</p>
2244

23-
<figure className="canvas-stack-sm">
24-
<img
25-
alt={mediaAssets.lunarFlyby.earthset.alt}
26-
className="max-h-screen w-full rounded-md object-cover"
27-
src={mediaAssets.lunarFlyby.earthset.src}
28-
/>
29-
<p className="canvas-text-caption text-muted-foreground">
30-
{mediaAssets.lunarFlyby.earthset.caption}{" "}
31-
{mediaAssets.lunarFlyby.earthset.credit}.
32-
</p>
33-
</figure>
45+
<Carousel
46+
onFocus={() => setIsCarouselPaused(true)}
47+
onBlur={() => setIsCarouselPaused(false)}
48+
onMouseEnter={() => setIsCarouselPaused(true)}
49+
onMouseLeave={() => setIsCarouselPaused(false)}
50+
onPointerCancel={() => setIsCarouselPaused(false)}
51+
onPointerDown={() => setIsCarouselPaused(true)}
52+
onPointerUp={() => setIsCarouselPaused(false)}
53+
opts={{ align: "start", loop: true }}
54+
setApi={setCarouselApi}
55+
>
56+
<CarouselContent>
57+
{gallery.map((asset, index) => (
58+
<CarouselItem key={asset.src}>
59+
<figure className="canvas-stack-sm">
60+
<div className="relative aspect-[16/10] max-h-screen overflow-hidden rounded-md bg-muted">
61+
<img
62+
alt={asset.alt}
63+
className="h-full w-full object-cover"
64+
src={asset.src}
65+
/>
66+
<div className="absolute top-3 left-3 rounded-full bg-background/85 px-3 py-1 text-xs text-foreground shadow-sm backdrop-blur">
67+
{index + 1} / {gallery.length}
68+
</div>
69+
</div>
70+
<p className="canvas-text-caption text-muted-foreground">
71+
{asset.caption} {asset.credit}.
72+
</p>
73+
</figure>
74+
</CarouselItem>
75+
))}
76+
</CarouselContent>
77+
</Carousel>
3478
</div>
3579

3680
<div className="canvas-grid-gap md:grid-cols-2">

packages/cli/src/host/artifact/artifact-runtime.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,19 +256,41 @@ describe("ArtifactRuntimeController", () => {
256256
})
257257

258258
it("reloads the mounted artifact when the registry version changes", async () => {
259+
const firstMountedElement = { remove: vi.fn() }
260+
const secondMountedElement = { remove: vi.fn() }
261+
const element = {
262+
appendChild: vi.fn(),
263+
ownerDocument: {
264+
createElement: vi
265+
.fn()
266+
.mockReturnValueOnce(firstMountedElement)
267+
.mockReturnValueOnce(secondMountedElement),
268+
},
269+
replaceChildren: vi.fn(),
270+
} as unknown as HTMLElement
271+
const firstDispose = vi.fn()
272+
const secondDispose = vi.fn()
259273
const importModule = vi
260274
.fn()
261-
.mockResolvedValueOnce(moduleWithDispose())
262-
.mockResolvedValueOnce(moduleWithDispose())
275+
.mockResolvedValueOnce(moduleWithDispose(firstDispose))
276+
.mockResolvedValueOnce(moduleWithDispose(secondDispose))
263277
const controller = new ArtifactRuntimeController({ importModule })
264-
controller.setElement(createElement())
278+
controller.setElement(element)
265279

266280
await controller.load("agent-html/artifacts/demo.artifact.tsx", 1)
267281
await controller.load("agent-html/artifacts/demo.artifact.tsx", 2)
268282

269283
expect(importModule).toHaveBeenCalledTimes(2)
270284
expect(importModule.mock.calls[0]?.[0]).toContain("v=1")
271285
expect(importModule.mock.calls[1]?.[0]).toContain("v=2")
286+
expect(firstDispose).toHaveBeenCalledOnce()
287+
expect(secondDispose).not.toHaveBeenCalled()
288+
expect(firstMountedElement.remove).toHaveBeenCalledOnce()
289+
expect(secondMountedElement.remove).not.toHaveBeenCalled()
290+
expect(controller.getSnapshot()).toMatchObject({
291+
mountedFilePath: "agent-html/artifacts/demo.artifact.tsx",
292+
status: "mounted",
293+
})
272294
})
273295

274296
it("does not retry module graph failures on a timer", async () => {

packages/cli/src/host/artifact/artifact-runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ export class ArtifactRuntimeController {
355355
})
356356
this.#onMounted()
357357

358-
if (previousDispose && previousMountedFilePath !== filePath) {
358+
if (previousDispose) {
359359
await this.#disposePrevious({
360360
dispose: previousDispose,
361361
filePath: previousMountedFilePath ?? filePath,

0 commit comments

Comments
 (0)