From 37f7a7a8dbaecb4cab0f5f520386d736fbd92d84 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 28 Jul 2026 18:16:01 -0700 Subject: [PATCH] [eric] dashboard: docked APPS suppress their tether too; tiled geometry never transitions (occluded-window transitions pin at t=0 and freeze tiles); tiles + dock measures re-apply on window reveal --- .../src/app/pages/Dashboard/cards/BrowserCard.tsx | 2 ++ .../app/pages/Dashboard/cards/DashboardViewCard.tsx | 2 ++ frontend/src/app/pages/Dashboard/cards/tileZones.ts | 12 +++++++++++- .../app/pages/Dashboard/geometry/dashboardTethers.ts | 4 ++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx index 1e46cb18..4f6f1c64 100644 --- a/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/BrowserCard.tsx @@ -271,11 +271,13 @@ const BrowserCard: React.FC = ({ // A RO only fires on slot RESIZE; the chat tiling/untiling MOVES the slot without resizing the // window, so re-measure on camera writes + settle timers or the docked card lags behind. window.addEventListener('openswarm:canvas-pan-changed', measure); + document.addEventListener('visibilitychange', measure); const timers = [60, 250, 700].map((ms) => window.setTimeout(measure, ms)); return () => { ro.disconnect(); window.removeEventListener('resize', measure); window.removeEventListener('openswarm:canvas-pan-changed', measure); + document.removeEventListener('visibilitychange', measure); timers.forEach((tm) => window.clearTimeout(tm)); }; // dockParentCard x/y/w/h are re-measure triggers: the slot's client rect moves with the chat card. diff --git a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx index 045a8383..fe96abae 100644 --- a/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx +++ b/frontend/src/app/pages/Dashboard/cards/DashboardViewCard.tsx @@ -197,11 +197,13 @@ const DashboardViewCard: React.FC = ({ // A RO only fires on slot RESIZE; the chat tiling/untiling MOVES the slot without resizing the // window, so re-measure on camera writes + settle timers or the docked card lags behind. window.addEventListener('openswarm:canvas-pan-changed', measure); + document.addEventListener('visibilitychange', measure); const timers = [60, 250, 700].map((ms) => window.setTimeout(measure, ms)); return () => { ro.disconnect(); window.removeEventListener('resize', measure); window.removeEventListener('openswarm:canvas-pan-changed', measure); + document.removeEventListener('visibilitychange', measure); timers.forEach((tm) => window.clearTimeout(tm)); }; }, [dockedTo, dockParentExpanded, dockParentTiled, dockParentCard?.x, dockParentCard?.y, dockParentCard?.width, dockParentCard?.height, getCanvasState, dockParentCard]); diff --git a/frontend/src/app/pages/Dashboard/cards/tileZones.ts b/frontend/src/app/pages/Dashboard/cards/tileZones.ts index a7d3e2cb..669d0ea6 100644 --- a/frontend/src/app/pages/Dashboard/cards/tileZones.ts +++ b/frontend/src/app/pages/Dashboard/cards/tileZones.ts @@ -123,7 +123,7 @@ export function useTiledStyle( ? el : (el.parentElement as HTMLElement | null) ?? el; const posProps = ['left', 'top']; - const sizeProps = ['width', 'height', 'transform', 'transform-origin']; + const sizeProps = ['width', 'height', 'transform', 'transform-origin', 'transition']; const clearAll = (): void => { posProps.forEach((pr) => posEl.style.removeProperty(pr)); sizeProps.forEach((pr) => el.style.removeProperty(pr)); @@ -142,10 +142,18 @@ export function useTiledStyle( el.style.setProperty('height', `${s.height}px`, 'important'); el.style.setProperty('transform', s.transform, 'important'); el.style.setProperty('transform-origin', s.transformOrigin, 'important'); + // Transitions OUTRANK inline important in the cascade, and in an occluded window they pin at + // t=0 forever, freezing the tile at stale geometry. Tiled cards track the camera instantly + // by design, so geometry must never animate here. + el.style.setProperty('transition', 'none', 'important'); }; apply(); window.addEventListener('openswarm:canvas-pan-changed', apply); window.addEventListener('resize', apply); + // An occluded window freezes rAF (framer, the tracker below), so geometry set while hidden can + // land stale; re-apply the moment the window is visible again. + document.addEventListener('visibilitychange', apply); + window.addEventListener('focus', apply); // The workspace can change size WITHOUT a window resize (chrome collapsing on fullscreen-enter, // a banner appearing). These inline writes OVERRIDE the class styles, so they must re-measure on // the same signals the React path uses or the tile keeps the pre-collapse viewport (the @@ -170,6 +178,8 @@ export function useTiledStyle( return () => { window.removeEventListener('openswarm:canvas-pan-changed', apply); window.removeEventListener('resize', apply); + document.removeEventListener('visibilitychange', apply); + window.removeEventListener('focus', apply); ro.disconnect(); timers.forEach((tm) => window.clearTimeout(tm)); window.cancelAnimationFrame(raf); diff --git a/frontend/src/app/pages/Dashboard/geometry/dashboardTethers.ts b/frontend/src/app/pages/Dashboard/geometry/dashboardTethers.ts index d8a6f0c7..438268ef 100644 --- a/frontend/src/app/pages/Dashboard/geometry/dashboardTethers.ts +++ b/frontend/src/app/pages/Dashboard/geometry/dashboardTethers.ts @@ -266,8 +266,8 @@ export function useTethers({ // An "app:" glow key targets a VIEW card (AppAgent driving an app); everything else is a browser card. const glowTarget = (id: string) => (id.startsWith('app:') ? viewCards[id.slice(4)] : browserCards[id]); for (const [browserId, { sourceId, fading, label }] of Object.entries(glowingBrowserCards)) { - // A docked browser renders INSIDE its chat; an arrow from the chat to it points at nothing. - if (browserCards[browserId]?.docked_to) continue; + // A docked card (browser OR app) renders INSIDE its chat; an arrow to it points at nothing. + if (glowTarget(browserId)?.docked_to) continue; if (sourceIsCollapsedChat(sourceId)) continue; const t = cardTether( glowTarget(browserId),