feat(standalone): "open in a new tab" escape hatch on OOM dialog (0002)

The automatic recovery uses an in-place location.replace reload, which does NOT
reliably release the OOM'd wasm heap in every browser (notably Firefox), so the
respawn can land right back in an out-of-memory state.

Add a user-gesture escape hatch to the terminal MemoryExhaustedDialog: an "Open
in a new tab" button that opens the editor in a fresh top-level browsing context
(clean retry chain) and closes the current tab. Because it runs from a real
click, window.open is not popup-blocked; window.close() may still be refused on
the original user-opened tab, which is fine — the new tab is the survivor. Copy
tells the user this sometimes helps where the in-place reload didn't.

- oom-watch.ts: export respawnInNewTab(win) — fresh-tab respawn with ?oomRetry
  cleared.
- MemoryExhaustedDialog: "Open in a new tab" (primary) + "Reload this tab"
  (secondary), with honest copy about Firefox + unsaved edits.
- WasmTool: wire onOpenNewTab to respawnInNewTab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergő Törcsvári 2026-06-10 15:41:18 +02:00
commit 0773acefaa
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
3 changed files with 61 additions and 10 deletions

View file

@ -13,7 +13,7 @@ import { memfsProjectDir } from "@/wasm/constants";
import { driveProjectIntoTool, type ToolFile } from "@/wasm/kicad-runner";
import type { CollabWindow } from "@/wasm/collab";
import { clog, cwarn } from "@/wasm/collab/debug";
import { createOomWatch } from "@/recovery/oom-watch";
import { createOomWatch, respawnInNewTab } from "@/recovery/oom-watch";
import { MemoryExhaustedDialog } from "@/recovery/MemoryExhaustedDialog";
// Tools with a working collab bridge (kicadCollabSnapshot/Apply embind exports).
@ -349,7 +349,10 @@ export function WasmTool({
<div id="window-container" />
{oomExhausted && (
<MemoryExhaustedDialog onReload={() => window.location.reload()} />
<MemoryExhaustedDialog
onOpenNewTab={() => respawnInNewTab()}
onReload={() => window.location.reload()}
/>
)}
{status && (