fix(editor): the white screen was React unmounting its own crash reporter
All three prod crash cascades (v0.1.19–21) ended white for the same reason: the final trap lands inside a child's EFFECT (an embind call reached through a react-query subscription), React unmounts the entire root, and the fatal overlay + console panel die with the tree they were built to survive. - WasmErrorBoundary: crash-capable children live inside it; the fatal screen and the console panel live OUTSIDE and keep rendering. WasmTool state (logs included) survives a descendant render/effect throw. - The fatal screen is now an actual blue screen (:( + solid blue), and every promotion path (window error, unhandled rejection, worker error, boundary) auto-opens the console — the log is the only account of what was loading. - tests/web/fatal-overlay.spec.ts pins the contract: a terminal uncaught error after boot ⇒ visible fatal overlay + open console with the [fatal] record. Green locally (23.9s). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
This commit is contained in:
parent
208cb67959
commit
92e02a54f9
2 changed files with 122 additions and 24 deletions
52
tests/web/fatal-overlay.spec.ts
Normal file
52
tests/web/fatal-overlay.spec.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* The blue screen of death must actually appear (WSOD hardening, async/16
|
||||
* companion). Three prod crash reports in a row (v0.1.19–21) ended in a WHITE
|
||||
* page because the crash killed the very UI built to report it: the final
|
||||
* trap landed inside a React effect, React unmounted the root, and the fatal
|
||||
* overlay + console panel died with it. The fix is layered — window/worker
|
||||
* error listeners promote a `fatal` state rendered OUTSIDE a WasmErrorBoundary
|
||||
* that absorbs descendant render/effect throws — and this spec pins the
|
||||
* user-visible contract:
|
||||
*
|
||||
* a terminal-looking uncaught error after boot ⇒ the blue fatal overlay is
|
||||
* VISIBLE and the console log panel is OPEN (the log is the only account of
|
||||
* what was loading; a collapsed console behind a mystery screen is useless).
|
||||
*
|
||||
* The throw is synthesized (a real wasm trap is not deterministically
|
||||
* available), but it exercises the identical promotion path: uncaught error →
|
||||
* terminal() match → promote → overlay + console.
|
||||
*/
|
||||
|
||||
const SCOPE = 'default';
|
||||
|
||||
test('a terminal uncaught error raises the blue fatal overlay with the console open', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(300000); // one full pcbnew wasm boot
|
||||
|
||||
await page.goto(`/${SCOPE}/projects/demo/demo.kicad_pcb?user=fatal-probe`);
|
||||
await expect(page.locator('#canvas')).toBeVisible({ timeout: 120000 });
|
||||
await expect
|
||||
.poll(() => page.title(), {
|
||||
message: 'editor never reached the expected title',
|
||||
timeout: 120000,
|
||||
intervals: [1000],
|
||||
})
|
||||
.toMatch(/demo — PCB Editor/i);
|
||||
|
||||
// Synthesize the trap: an uncaught error whose message matches the
|
||||
// terminal signature set (Firefox's bare wasm trap spelling included).
|
||||
await page.evaluate(() => {
|
||||
setTimeout(() => {
|
||||
throw new Error('index out of bounds');
|
||||
}, 0);
|
||||
});
|
||||
|
||||
await expect(page.getByTestId('fatal-overlay')).toBeVisible({ timeout: 10000 });
|
||||
// The console panel auto-opened and carries the promotion record.
|
||||
await expect(page.locator('pre').filter({ hasText: '[fatal]' })).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue