From b722dbd2f8f80dad9c0f02cbb74c2df6e026f682 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 4 Aug 2026 20:51:47 -0700 Subject: [PATCH] [eric] chat: the frozen shot and the live mini never paint together, the misaligned double-page overlap dies --- frontend/src/app/pages/AgentChat/AgentChat.tsx | 2 ++ .../app/pages/Dashboard/cards/BrowserCard.tsx | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index 9bdd382f..c74425a5 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -283,6 +283,8 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose borderRadius: '12px', overflow: 'hidden', border: `1px solid ${c.border.medium}`, + // The live overlay stamps data-mini-live; while it paints, the frozen-shot backdrop must not (the clamped overlay leaves margins where a misaligned second copy of the page peeked through). + '&[data-mini-live="1"] img': { opacity: 0 }, } as const; // A live webview cannot be clipped by the scroller, so the OVERLAY only shows while fully in view; this frozen shot is what scrolls and clips underneath it, ChatGPT-style. const dockedShot = dockedSurfaceId ? getMinimizedShot(dockedSurfaceId) : undefined; diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx index 4cefdf93..bb18808c 100644 --- a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx @@ -318,6 +318,22 @@ const BrowserCard: React.FC = ({ }, [dockedTo, dockParentExpanded, dockParentTiled, dockParentCard?.x, dockParentCard?.y, dockParentCard?.width, dockParentCard?.height, getCanvasState, dockParentCard]); const dockParentZ = dockParentCard?.zOrder ?? 0; + // The slot's frozen-shot backdrop and the live overlay must never BOTH paint (the clamped overlay leaves margins where a misaligned copy of the page peeks through), so the card stamps its live state onto the slot and the slot's CSS hides the shot while live. + const overlayLiveRef = useRef(false); + useEffect(() => { + if (!dockedTo) return undefined; + const stamp = (): void => { + const slot = document.querySelector(`[data-browser-slot="${dockedTo}"]`); + slot?.setAttribute('data-mini-live', overlayLiveRef.current ? '1' : '0'); + }; + stamp(); + const t = window.setInterval(stamp, 400); + return () => { + window.clearInterval(t); + document.querySelector(`[data-browser-slot="${dockedTo}"]`)?.setAttribute('data-mini-live', '0'); + }; + }, [dockedTo]); + // The chat drags on a per-frame compositor transform, but dock geometry only re-measures at settle; ride the same drag channel imperatively or the mini visibly trails its own chat. const hasDockRect = !!dockRect; useEffect(() => {