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:
parent
0d2f88b437
commit
0773acefaa
3 changed files with 61 additions and 10 deletions
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -4,20 +4,38 @@ import { BlockingDialog } from "@/preflight/BlockingDialog";
|
|||
* Terminal "not enough memory" UI for feature 0002 — shown after the OOM retry
|
||||
* chain hits MAX_RETRIES. Reuses 0001's blocking dialog for visual consistency.
|
||||
*
|
||||
* The copy is honest about the two limitations the spec calls out: a respawn
|
||||
* re-opens the same file but loses in-tab edits not yet pushed to the backend /
|
||||
* Y.Doc, and `window.close()` may not close the very first (user-opened) tab.
|
||||
* Offers two manual actions:
|
||||
* - "Open in a new tab" (primary): a user-gesture respawn in a fresh browsing
|
||||
* context. This sometimes succeeds where the automatic in-place reload did
|
||||
* not, because a brand-new tab is the most reliable way to actually release
|
||||
* the OOM'd wasm heap (an in-place reload doesn't, reliably, in every
|
||||
* browser — notably Firefox).
|
||||
* - "Reload this tab" (secondary): the simple in-place retry.
|
||||
*
|
||||
* Copy is honest about lost unsaved edits (a respawn re-opens the same file but
|
||||
* loses in-tab edits not yet synced to the backend / Y.Doc).
|
||||
*/
|
||||
export function MemoryExhaustedDialog({ onReload }: { onReload?: () => void }) {
|
||||
export function MemoryExhaustedDialog({
|
||||
onOpenNewTab,
|
||||
onReload,
|
||||
}: {
|
||||
onOpenNewTab?: () => void;
|
||||
onReload?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<BlockingDialog
|
||||
title="Your device ran out of memory"
|
||||
description="The editor tried to recover a few times but kept running out of memory, so it stopped to avoid looping. This design is likely too large for this device's available memory."
|
||||
reasons={[
|
||||
{
|
||||
title: "What you can try",
|
||||
title: "Try a fresh tab",
|
||||
detail:
|
||||
"Close other tabs and applications to free memory, use a desktop machine with more RAM, or open a smaller design.",
|
||||
"Opening the editor in a brand-new browser tab (and closing this one) sometimes frees enough memory to continue — especially in Firefox, where reloading in place may not fully release the previous session's memory.",
|
||||
},
|
||||
{
|
||||
title: "Free up memory",
|
||||
detail:
|
||||
"Close other tabs and applications, use a desktop machine with more RAM, or open a smaller design.",
|
||||
},
|
||||
{
|
||||
title: "Unsaved changes",
|
||||
|
|
@ -26,10 +44,13 @@ export function MemoryExhaustedDialog({ onReload }: { onReload?: () => void }) {
|
|||
},
|
||||
]}
|
||||
primary={
|
||||
onReload
|
||||
? { label: "Reload and try again", onClick: onReload }
|
||||
onOpenNewTab
|
||||
? { label: "Open in a new tab", onClick: onOpenNewTab }
|
||||
: undefined
|
||||
}
|
||||
secondary={
|
||||
onReload ? { label: "Reload this tab", onClick: onReload } : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,6 +308,33 @@ export function createOomWatch(opts: OomWatchOptions): OomWatch {
|
|||
return { start, stop, onAbort };
|
||||
}
|
||||
|
||||
/**
|
||||
* User-gesture escape hatch for the terminal dialog: open the editor in a brand
|
||||
* new tab with a clean retry chain, then close this one.
|
||||
*
|
||||
* This is deliberately NOT the automatic recovery path: it runs from a real
|
||||
* click, so `window.open` is not popup-blocked, and a fresh top-level browsing
|
||||
* context is the most reliable way to actually drop the OOM'd wasm heap — an
|
||||
* in-place `location.replace` reload does not reliably release it in every
|
||||
* browser (notably Firefox). `window.close()` may still be refused on the
|
||||
* original, user-opened tab; that's fine — the new tab is the working one and
|
||||
* the user can close the old one. Returns whether a new tab was opened.
|
||||
*/
|
||||
export function respawnInNewTab(win: Window = window): boolean {
|
||||
const url = new URL(win.location.href);
|
||||
url.searchParams.delete(RETRY_PARAM); // fresh chain — this is a manual retry
|
||||
const opened = win.open(url.toString(), "_blank");
|
||||
if (opened) {
|
||||
try {
|
||||
win.close();
|
||||
} catch {
|
||||
/* original tab may refuse to close — new tab is the survivor anyway */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function safeLocalStorage(win: Window): Storage | null {
|
||||
try {
|
||||
return win.localStorage;
|
||||
|
|
|
|||
Loading…
Reference in a new issue