From 0326bc45f75521069d44bc6a8b7a2fa82593eef9 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 4 Aug 2026 21:47:12 -0700 Subject: [PATCH] [eric] chat: the mini tracks streaming transcript mutations, appends can no longer bounce it between stale and fresh rects --- frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx index 267dec57..361d45ab 100644 --- a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx @@ -258,6 +258,7 @@ const BrowserCard: React.FC = ({ let scrollRaf = 0; const onScroll = (): void => { if (!scrollRaf) scrollRaf = requestAnimationFrame(() => { scrollRaf = 0; measure(); }); }; const ro = new ResizeObserver(() => measure()); + const mo = new MutationObserver(() => onScroll()); const measure = (): void => { const slot = document.querySelector(`[data-browser-slot="${dockedTo}"]`); const layer = rootElRef.current?.parentElement; @@ -271,6 +272,9 @@ const BrowserCard: React.FC = ({ scrollHost?.removeEventListener('scroll', onScroll); scrollHost = slot.closest('[data-chat-transcript]'); scrollHost?.addEventListener('scroll', onScroll, { passive: true }); + // Streaming appends move/remount the slot without resizing or scrolling anything observed; the mutation observer is what keeps the mini glued during a live turn. + mo.disconnect(); + if (scrollHost) mo.observe(scrollHost, { childList: true, subtree: true }); hookedSlot = slot; } const z = getCanvasState().zoom || 1; @@ -305,10 +309,11 @@ const BrowserCard: React.FC = ({ window.addEventListener('openswarm:canvas-pan-changed', measure); document.addEventListener('visibilitychange', measure); const timers = [60, 250, 700].map((ms) => window.setTimeout(measure, ms)); - // Self-heal: the slot lives in the WINDOWED transcript and remounts without firing any of the events above, which left the mini scaled against a stale rect (visibly overflowing its frame) until the next pan. 500ms bounds how long any staleness can survive. - const heal = window.setInterval(measure, 500); + // Self-heal: the slot lives in the WINDOWED transcript and remounts without firing any of the events above, which left the mini scaled against a stale rect (visibly overflowing its frame) until the next pan. 150ms bounds how long any staleness can survive. + const heal = window.setInterval(measure, 150); return () => { ro.disconnect(); + mo.disconnect(); window.removeEventListener('resize', measure); window.removeEventListener('openswarm:canvas-pan-changed', measure); document.removeEventListener('visibilitychange', measure);