From 197f317bfb42f618184aa0d16d77e7f72621786d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Fri, 31 Jul 2026 20:33:55 +0200 Subject: [PATCH] fix(editor): fatal overlay matches Firefox trap messages + taps pthread-worker errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.1.19 prod crash (console-export-2026-7-31_17-49-20.log) opened with Firefox's bare "index out of bounds" — no "RuntimeError" prefix, no "table" — which the terminal-signature regex only knew in Chrome's spelling, so the overlay this feature exists for never promoted on the very trap it was built against. Match the bare form (+ "null function or function signature", the other Firefox spelling in this family). Also wrap the Worker constructor attach-only: a pthread worker's uncaught error fires an ErrorEvent on the Worker OBJECT, never on window, so worker crashes (raytracer pool etc.) were invisible to both listeners. The editor's main()/wx run on the page thread (no PROXY_TO_PTHREAD on the link line) — the tap is defense-in-depth, not the primary fix. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4 --- web/standalone/src/components/WasmTool.tsx | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/web/standalone/src/components/WasmTool.tsx b/web/standalone/src/components/WasmTool.tsx index ad62cb8..47c64e0 100644 --- a/web/standalone/src/components/WasmTool.tsx +++ b/web/standalone/src/components/WasmTool.tsx @@ -1185,8 +1185,12 @@ export function WasmTool({ // genuinely terminal signatures promote to the fatal overlay; ordinary app // errors must not hijack a working editor. React.useEffect(() => { + // NOTE: matched against `e.error.message`, which is BARE — Firefox's first + // trap is literally "index out of bounds" (no "RuntimeError", no "table") + // and slipped through the original pattern; the v0.1.19 prod log opens with + // exactly that message. const terminal = (msg: string) => - /RuntimeError|\babort(ed)?\b|table index is out of bounds|indirect call signature|memory access out of bounds|unreachable executed/i.test( + /RuntimeError|\babort(ed)?\b|\bindex out of bounds|indirect call signature|memory access out of bounds|unreachable executed|null function or function signature/i.test( msg, ); const onError = (e: ErrorEvent) => { @@ -1203,9 +1207,35 @@ export function WasmTool({ append(dumpTrace()); setFatal(msg); }; + // With PROXY_TO_PTHREAD, main()/wx/timers — and therefore every asyncify + // trap in this family — throw INSIDE a pthread worker. A worker's uncaught + // error fires an ErrorEvent on the Worker OBJECT, never on `window`, so the + // two listeners below can't see the very traps this overlay exists for + // (v0.1.19 prod: three "Uncaught RuntimeError"s, overlay never promoted). + // Wrap the constructor attach-only: the glue spawns all pthread workers + // from this realm, so every one gets an error tap. + const NativeWorker = window.Worker; + const onWorkerError = (e: ErrorEvent) => { + const msg = String(e.message ?? ""); + if (!terminal(msg)) return; + append(`[fatal] worker error: ${msg}`); + append(dumpTrace()); + setFatal(msg); + }; + const PatchedWorker = function ( + this: unknown, + ...args: ConstructorParameters + ) { + const w = new NativeWorker(...args); + w.addEventListener("error", onWorkerError); + return w; + } as unknown as typeof Worker; + PatchedWorker.prototype = NativeWorker.prototype; + window.Worker = PatchedWorker; window.addEventListener("error", onError); window.addEventListener("unhandledrejection", onRejection); return () => { + window.Worker = NativeWorker; window.removeEventListener("error", onError); window.removeEventListener("unhandledrejection", onRejection); };