diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/deleteSelectedCards.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/deleteSelectedCards.ts index 3f134aa1..a2002f7a 100644 --- a/frontend/src/app/pages/Dashboard/hooks/interaction/deleteSelectedCards.ts +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/deleteSelectedCards.ts @@ -1,7 +1,7 @@ import { closeSession } from '@/shared/state/agentsSlice'; import { removeNote, removeWorkflowCard, closeWorkflowsHub, recordClosedCard } from '@/shared/state/dashboardLayoutSlice'; import { closeWorkflowCard } from '@/shared/state/workflowsSlice'; -import { removeBrowserCardCleanly } from '@/shared/browserTeardown'; +import { removeBrowserCardsCleanly } from '@/shared/browserTeardown'; import { removeViewCardCleanly } from '@/shared/viewTeardown'; import type { AppDispatch } from '@/shared/state/store'; import type { CardType } from '../state/useDashboardSelection'; @@ -36,6 +36,6 @@ export function deleteSelectedCards(selectedIds: Map, dispatch // "non-existent mailbox" errors and SIGSEGVs the GPU/browser process (the mass-delete self-quit). void (async () => { for (const id of viewIds) await removeViewCardCleanly(id, dispatch); - for (const id of browserIds) await removeBrowserCardCleanly(id, dispatch); + await removeBrowserCardsCleanly(browserIds, dispatch); })(); } diff --git a/frontend/src/shared/browserTeardown.ts b/frontend/src/shared/browserTeardown.ts index 6adfcb5e..c225b6df 100644 --- a/frontend/src/shared/browserTeardown.ts +++ b/frontend/src/shared/browserTeardown.ts @@ -38,3 +38,17 @@ export async function removeBrowserCardCleanly( forgetBrowser(browserId); dispatch(removeBrowserCard(browserId)); } + +// Batch remove: detach every browser's CDP in PARALLEL (order-independent, the only invariant is +// all debuggers gone before any unmount), THEN remove them. Serial detach cost ~72ms/browser, so a +// 20-card multi-select delete lagged over a second; this keeps the crash-safe ordering but flat. +export async function removeBrowserCardsCleanly( + browserIds: string[], + dispatch: Dispatch, +): Promise { + await Promise.allSettled(browserIds.map((id) => detachBrowserCdp(id))); + for (const id of browserIds) { + forgetBrowser(id); + dispatch(removeBrowserCard(id)); + } +}