From 7ea9286aa4f5b7aeced421b99276634a3e99c4df Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 1 Sep 2026 16:17:47 +0200 Subject: [PATCH] fix(app): keep agents running when image viewer closes (#4161) The shared image lightbox handled Escape after the global shortcut dispatcher had already routed the same key to agent interruption. Register the viewer with the established web overlay owner so dismissal takes first refusal while ordinary Escape interruption remains intact. --- .../browser/agent-tab-image-stability.spec.ts | 10 +++++- .../components/attachment-lightbox.test.tsx | 2 ++ .../src/components/attachment-lightbox.tsx | 31 +++++++++++-------- 3 files changed, 29 insertions(+), 14 deletions(-) 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) - +