[eric] browser: a replaced spawned browser is torn down through the full path, the layout sync can no longer resurrect it

This commit is contained in:
ciregenz
2026-08-04 21:50:08 -07:00
parent 0326bc45f7
commit 5d6720ceab
2 changed files with 8 additions and 6 deletions
@@ -589,13 +589,9 @@ function addMissingCards<T extends { x: number; y: number; width: number; height
function clearOtherDocks(state: { browserCards: Record<string, BrowserCardPosition>; viewCards: Record<string, ViewCardPosition> }, 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)) {
@@ -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 }));