From efb87d62286e1ee113c9f80fc5181a327629938f Mon Sep 17 00:00:00 2001 From: claude-ceo-assistant Date: Mon, 25 May 2026 01:45:36 -0700 Subject: [PATCH] fix: contain attachment previews in chat panel --- .../components/tabs/chat/AttachmentImage.tsx | 3 +- .../tabs/chat/AttachmentLightbox.tsx | 29 +++++++++++++------ .../components/tabs/chat/AttachmentPDF.tsx | 7 +++-- .../__tests__/AttachmentLightbox.test.tsx | 18 +++++++++++- .../chat/__tests__/AttachmentPDF.test.tsx | 6 ++-- .../chat/__tests__/AttachmentPreview.test.tsx | 6 ++-- 6 files changed, 51 insertions(+), 18 deletions(-) diff --git a/canvas/src/components/tabs/chat/AttachmentImage.tsx b/canvas/src/components/tabs/chat/AttachmentImage.tsx index a123856fb..79c8c7c1c 100644 --- a/canvas/src/components/tabs/chat/AttachmentImage.tsx +++ b/canvas/src/components/tabs/chat/AttachmentImage.tsx @@ -166,11 +166,12 @@ export function AttachmentImage({ workspaceId, attachment, onDownload, tone }: P open={open} onClose={() => setOpen(false)} ariaLabel={`Preview of ${attachment.name}`} + contained > {attachment.name} diff --git a/canvas/src/components/tabs/chat/AttachmentLightbox.tsx b/canvas/src/components/tabs/chat/AttachmentLightbox.tsx index 09f4cb016..f52c0bc87 100644 --- a/canvas/src/components/tabs/chat/AttachmentLightbox.tsx +++ b/canvas/src/components/tabs/chat/AttachmentLightbox.tsx @@ -1,6 +1,6 @@ "use client"; -// AttachmentLightbox — shared fullscreen modal for image / PDF / +// AttachmentLightbox — shared modal for image / PDF / // (future) any-fullscreen-renderable kind. Owns: // - Backdrop + centered viewport // - Esc to close @@ -14,11 +14,11 @@ // // Design choices: // -// 1. Portals — we don't use ReactDOM.createPortal because the canvas -// chat surface already renders at a high z-index and the modal's -// fixed-position layout reaches the viewport regardless. Saves a -// portal mount in the common case + avoids the SSR warning (canvas -// is "use client" but the parent shell is server-rendered). +// 1. Portals — we don't use ReactDOM.createPortal because the chat tab +// already gives us a positioned container and the preview should stay +// inside that panel. Saves a portal mount in the common case + avoids +// the SSR warning (canvas is "use client" but the parent shell is +// server-rendered). // // 2. Focus trap — inline implementation (not a 3rd-party dep). The // chat lightbox needs to trap focus only across two interactive @@ -41,13 +41,17 @@ interface Props { * the dialog opens. The caller knows what's inside (image alt * text, PDF filename) and supplies it. */ ariaLabel: string; + /** Constrain the preview to the nearest positioned ancestor instead + * of the whole browser viewport. ChatTab passes this so previews + * stay inside the active side-panel tab. */ + contained?: boolean; /** The thing being shown in fullscreen — , , etc. * Caller is responsible for sizing it to fit the viewport (we * give it max-w-full max-h-full via CSS). */ children: ReactNode; } -export function AttachmentLightbox({ open, onClose, ariaLabel, children }: Props) { +export function AttachmentLightbox({ open, onClose, ariaLabel, contained = false, children }: Props) { const closeButtonRef = useRef(null); const previousFocusRef = useRef(null); @@ -90,12 +94,19 @@ export function AttachmentLightbox({ open, onClose, ariaLabel, children }: Props if (!open) return null; + const rootClass = contained + ? "absolute inset-0 z-50 flex items-center justify-center bg-black/85 motion-reduce:transition-none transition-opacity" + : "fixed inset-0 z-50 flex items-center justify-center bg-black/85 motion-reduce:transition-none transition-opacity"; + const contentClass = contained + ? "h-full w-full p-3 flex items-center justify-center" + : "max-w-[95vw] max-h-[90vh] flex items-center justify-center"; + return (
{/* Close button — top-right, large hit area, keyboard-focusable. @@ -112,7 +123,7 @@ export function AttachmentLightbox({ open, onClose, ariaLabel, children }: Props
e.stopPropagation()} > {children} diff --git a/canvas/src/components/tabs/chat/AttachmentPDF.tsx b/canvas/src/components/tabs/chat/AttachmentPDF.tsx index 8b519549a..9093ca691 100644 --- a/canvas/src/components/tabs/chat/AttachmentPDF.tsx +++ b/canvas/src/components/tabs/chat/AttachmentPDF.tsx @@ -19,8 +19,8 @@ // suppress the toolbar; we keep it on so the user gets standard // PDF affordances. // -// Fullscreen: AttachmentLightbox hosts the PDF at viewport size on -// click. Same shared modal as image — third caller justifies the +// Preview: AttachmentLightbox hosts the PDF inside the active chat tab +// on click. Same shared modal as image — third caller justifies the // abstraction (per RFC #2991 design). // // Failure modes: @@ -144,8 +144,9 @@ export function AttachmentPDF({ workspaceId, attachment, onDownload, tone }: Pro open={open} onClose={() => setOpen(false)} ariaLabel={`Preview of ${attachment.name}`} + contained > -
+