diff --git a/packages/app/e2e/browser/agent-tab-image-stability.spec.ts b/packages/app/e2e/browser/agent-tab-image-stability.spec.ts index 320e52ba28..5f70bdafe3 100644 --- a/packages/app/e2e/browser/agent-tab-image-stability.spec.ts +++ b/packages/app/e2e/browser/agent-tab-image-stability.spec.ts @@ -14,6 +14,7 @@ import { switchAwayAndBackWithoutImageInstability, userPagesUntilAssistantImageRenders, } from "../support/helpers/assistant-images"; +import { expectAgentReadyToInterrupt } from "../support/helpers/agent-stream"; import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client"; const test = base.extend<{ imageWorkspace: SeededWorkspace }>({ @@ -84,8 +85,15 @@ test("opens a timeline image in a zoomable lightbox", async ({ .poll(async () => Math.round((await transformedContent.boundingBox())?.width ?? 0)) .toBe(Math.round(initialBox!.width)); - await canvas.click({ position: { x: 4, y: 4 } }); + await workspace.client.sendAgentMessage( + imageAgent.id, + "Stay running while the assistant image lightbox is closed.", + ); + await expectAgentReadyToInterrupt(page); + + await page.keyboard.press("Escape"); await expect(lightboxImage).toHaveCount(0); + await expectAgentReadyToInterrupt(page); }); test("reloading a timeline anchors near-tail assistant image growth", async ({ diff --git a/packages/app/src/components/attachment-lightbox.test.tsx b/packages/app/src/components/attachment-lightbox.test.tsx index 1495c07b6f..ef5aa1833a 100644 --- a/packages/app/src/components/attachment-lightbox.test.tsx +++ b/packages/app/src/components/attachment-lightbox.test.tsx @@ -136,6 +136,8 @@ let container: HTMLElement | null = null; beforeEach(() => { const dom = new JSDOM("
"); + dom.window.requestAnimationFrame = vi.fn(() => 1); + dom.window.cancelAnimationFrame = vi.fn(); vi.stubGlobal("React", React); vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); vi.stubGlobal("window", dom.window); diff --git a/packages/app/src/components/attachment-lightbox.tsx b/packages/app/src/components/attachment-lightbox.tsx index ff99c65f6d..3cf2c5203f 100644 --- a/packages/app/src/components/attachment-lightbox.tsx +++ b/packages/app/src/components/attachment-lightbox.tsx @@ -12,6 +12,7 @@ import { SPACING } from "@/styles/theme"; import { WindowChromeRootRegion } from "@/utils/desktop-window"; import { ZoomableImage } from "@/components/zoomable-viewport/image"; import type { ViewportSize } from "@/components/zoomable-viewport/geometry"; +import { useGlobalWebOverlayLayer, useWebOverlayRegistration } from "@/lib/overlay-root"; export type ImageLightboxSource = | { type: "attachment"; metadata: AttachmentMetadata } @@ -33,23 +34,27 @@ export function AttachmentLightbox({ source, onClose }: AttachmentLightboxProps) const url = source?.type === "uri" ? source.uri : attachmentUrl; const contentSize = source?.type === "uri" ? source.contentSize : undefined; const [errored, setErrored] = useState(false); + const modalLayer = useGlobalWebOverlayLayer("modal", isWeb && source !== null); useEffect(() => { setErrored(false); }, [metadata?.id, url]); - useEffect(() => { - if (!isWeb || !source) return; - function handleKeydown(event: KeyboardEvent) { - if (event.key === "Escape") { - onClose(); - } - } - window.addEventListener("keydown", handleKeydown); - return () => { - window.removeEventListener("keydown", handleKeydown); - }; - }, [onClose, source]); + const handleWebOverlayKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key !== "Escape") return false; + event.preventDefault(); + event.stopPropagation(); + onClose(); + return true; + }, + [onClose], + ); + const setWebOverlayScope = useWebOverlayRegistration({ + active: isWeb && source !== null, + layer: modalLayer, + onKeyDown: handleWebOverlayKeyDown, + }); const contentLayerStyle = useMemo( () => [ @@ -87,7 +92,7 @@ export function AttachmentLightbox({ source, onClose }: AttachmentLightboxProps)