From 201216d1662d0afa8bc96f8a8c9785c69773bd3c Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 18 Aug 2026 19:04:56 -0700 Subject: [PATCH] [eric] canvas: new app cards anchor on the nearest on-canvas ancestor and docked rects stop being invisible obstacles (ENG-346) --- .../hooks/lifecycle/useDashboardLifecycle.ts | 13 +++++++++++-- frontend/src/shared/state/dashboardLayoutSlice.ts | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts b/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts index 02bdcd83..ef4fd1ae 100644 --- a/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts +++ b/frontend/src/app/pages/Dashboard/hooks/lifecycle/useDashboardLifecycle.ts @@ -370,13 +370,22 @@ export function useDashboardLifecycle({ if (sess.dashboard_id !== dashboardId) continue; autoOpenedOutputsRef.current.add(output.id); if (viewCards[output.id]) continue; - dispatch(addViewCard({ outputId: output.id, expandedSessionIds, parentSessionId: sid })); + // A sub-agent has no canvas card, so anchoring on it dumped the app at a far grid cell; walk up to the nearest ancestor that IS on the canvas (ENG-346). + const layoutCards = store.getState().dashboardLayout.cards; + let anchorSid: string = sid; + for (let hops = 0; hops < 5 && !layoutCards[anchorSid]; hops += 1) { + const up = sessions[anchorSid]?.parent_session_id; + if (!up || !sessions[up]) break; + anchorSid = up; + } + if (!layoutCards[anchorSid]) anchorSid = sid; + dispatch(addViewCard({ outputId: output.id, expandedSessionIds, parentSessionId: anchorSid })); const outputId = output.id; setTimeout(() => { const vc = store.getState().dashboardLayout.viewCards[outputId]; if (!vc) return; const rects = [{ x: vc.x, y: vc.y, width: vc.width, height: vc.height }]; - const ac = store.getState().dashboardLayout.cards[sid]; + const ac = store.getState().dashboardLayout.cards[anchorSid]; if (ac) rects.push({ x: ac.x, y: ac.y, width: ac.width, height: ac.height }); canvasActions.revealCards(rects); handleHighlightCard(outputId); diff --git a/frontend/src/shared/state/dashboardLayoutSlice.ts b/frontend/src/shared/state/dashboardLayoutSlice.ts index c50d8ab1..95f50ab8 100644 --- a/frontend/src/shared/state/dashboardLayoutSlice.ts +++ b/frontend/src/shared/state/dashboardLayoutSlice.ts @@ -418,10 +418,13 @@ function collectOccupiedRects( } for (const c of Object.values(state.viewCards)) { if (exclude?.type === 'view' && exclude.id === c.output_id) continue; + // Docked cards render inside a chat; their stored canvas rect is an invisible obstacle that flings new placements far away (ENG-346). + if (c.docked_to) continue; rects.push({ x: c.x, y: c.y, w: c.width, h: c.height }); } for (const c of Object.values(state.browserCards)) { if (exclude?.type === 'browser' && exclude.id === c.browser_id) continue; + if (c.docked_to) continue; rects.push({ x: c.x, y: c.y, w: c.width, h: c.height }); } for (const w of Object.values(state.workflowCards)) {