diff --git a/frontend/src/shared/state/dashboardLayoutSlice.ts b/frontend/src/shared/state/dashboardLayoutSlice.ts index 29ccdc0f..e2b067fb 100644 --- a/frontend/src/shared/state/dashboardLayoutSlice.ts +++ b/frontend/src/shared/state/dashboardLayoutSlice.ts @@ -589,13 +589,9 @@ function addMissingCards; viewCards: Record }, sessionId: string, keepBrowserId?: string): void { for (const bc of Object.values(state.browserCards)) { if (bc.docked_to !== sessionId) continue; - // The card being (re-)docked must survive its own clear: layout sync re-asserts docks, and deleting the assertee would vanish a live browser. + // The card being (re-)docked must survive its own clear: layout sync re-asserts docks. if (bc.browser_id === keepBrowserId) continue; - // An agent-owned browser displaced by its replacement is a corpse (the agent spins a fresh one when a tab dies); releasing it to the canvas painted dead stacked windows next to the chat. - if (bc.spawned_by === sessionId) { - delete state.browserCards[bc.browser_id]; - continue; - } + // Corpse cleanup happens in WebSocketManager via removeBrowserCardCleanly, NOT here: a bare store delete gets resurrected by the backend layout sync as a free card. bc.docked_to = null; } for (const vc of Object.values(state.viewCards)) { diff --git a/frontend/src/shared/ws/WebSocketManager.ts b/frontend/src/shared/ws/WebSocketManager.ts index cd297690..7b36e50c 100644 --- a/frontend/src/shared/ws/WebSocketManager.ts +++ b/frontend/src/shared/ws/WebSocketManager.ts @@ -843,6 +843,12 @@ class WebSocketManager { let glowLabel = 'Use Browser'; if (parentCard) { pos = placeBrowserBesideChat(layoutState, parentCard, parentId, browserCard.width, browserCard.height, browserCard.browser_id); + // A replacement browser buries its predecessor: the agent spins a fresh card when a tab dies, and the displaced spawned sibling must be torn down through the FULL path (a bare store delete gets resurrected by the backend layout sync as a free stacked window). + for (const old of Object.values(layoutState.browserCards)) { + if (old.browser_id !== browserCard.browser_id && old.spawned_by === parentId && !old.keep_open) { + void import('@/shared/browserTeardown').then(({ removeBrowserCardCleanly }) => removeBrowserCardCleanly(old.browser_id, store.dispatch)); + } + } // Default home is INSIDE the chat: the card overlays the chat's dock slot while the // chat is expanded; the beside-chat spot stays the undock/collapse fallback. store.dispatch(setBrowserDocked({ browserId: browserCard.browser_id, dockedTo: parentId }));