Two Windows production-only regressions found by installing and running the released v0.3.22 MSI on a clean machine (RTX 4070 / CUDA). 1. Black screen on every launch. The first-run splash crashes React during render with "Cannot access 'logs' before initialization" — an empty #root, i.e. a black window. Root cause: in BootstrapSplash the derived const `isUnrecoverable` (which reads the `logs` useState result) sat *between* two `useState` calls. esbuild's release minifier merges the declarations into one comma-list and can hoist the read ahead of its `useState` binding, producing a temporal-dead-zone access. Dev, unminified, and the e2e suite (which runs against the un-minified dev server) never trip it, so it shipped. #1159 fixed the source-visible ordering but left the derived value wedged among the hooks, so the minifier landmine remained. Fix: declare every hook before any derived const that reads a hook result — the minifier then has nothing to reorder. Verified deterministically: the shipped bundle contains exactly one use-before-init-useState hazard at this site; a build of this change contains zero, and the app renders. Guarded by tests/frontend/bootstrap-splash-hook-order. 2. Storm of console windows during first-run install. Bootstrap spawns dozens of helper processes (uv venv/sync, managed-python, capability probes); on Windows each flashed its own cmd-style console window because run_streaming and the direct probes never set CREATE_NO_WINDOW. Added one Windows-only `no_window` chokepoint in tools.rs and routed every bootstrap/tools/backend subprocess through it (mirrors the flag the backend spawn and nvidia-smi probe already set). stdout/stderr are still piped to the splash log. No-op on macOS/Linux — default behaviour there is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
2.5 KiB
JavaScript
46 lines
2.5 KiB
JavaScript
// Regression guard for the v0.3.22 Windows black screen (#<pr>).
|
|
//
|
|
// The bootstrap splash rendered a blank/black window on the shipped production
|
|
// build because a derived `const` that *reads* a `useState` result was declared
|
|
// BETWEEN hook calls:
|
|
//
|
|
// const isFailed = stage === 'failed';
|
|
// const [logs, setLogs] = useState([]);
|
|
// const isUnrecoverable = isFailed && isUnrecoverableFailure(message, logs);
|
|
// const [logsOpen, setLogsOpen] = useState(true); // <-- more hooks after
|
|
//
|
|
// The production minifier (esbuild) merges consecutive declarations into one
|
|
// comma-list and can hoist `isUnrecoverable` (which reads `logs`) AHEAD of the
|
|
// `[logs] = useState([])` binding, emitting `...isUnrecoverable=..logs.., [logs]=
|
|
// useState()..` — a temporal-dead-zone access that throws during render. React
|
|
// unmounts to an empty #root and the whole app is a black screen. Dev and
|
|
// unminified builds short-circuit on `isFailed` so they never trip it; only the
|
|
// minified release bundle did (which is why the e2e suite, run against the dev
|
|
// server, missed it).
|
|
//
|
|
// The fix is to declare every hook BEFORE any derived const that reads a hook
|
|
// result. This test locks that ordering in so the regression can't silently
|
|
// return: it asserts the `logs` useState precedes the `isUnrecoverable` read,
|
|
// and that no value-binding hook (useState/useRef/useReducer) appears after that
|
|
// read within the splash component body.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const src = readFileSync(
|
|
fileURLToPath(new URL('../../frontend/src/components/BootstrapSplash.jsx', import.meta.url)),
|
|
'utf8',
|
|
);
|
|
|
|
test('BootstrapSplash: the `logs` useState is declared before the derived const that reads it', () => {
|
|
const logsIdx = src.indexOf('const [logs, setLogs] = useState');
|
|
const derivedIdx = src.indexOf('const isUnrecoverable');
|
|
assert.ok(logsIdx > 0, "anchor 'const [logs, setLogs] = useState' not found — did BootstrapSplash get refactored? Update this guard.");
|
|
assert.ok(derivedIdx > 0, "anchor 'const isUnrecoverable' not found — did BootstrapSplash get refactored? Update this guard.");
|
|
assert.ok(
|
|
logsIdx < derivedIdx,
|
|
"`logs` useState must be declared BEFORE `isUnrecoverable` reads it. With it after, the production minifier hoists the read ahead of the binding → 'Cannot access logs before initialization' → black screen (the v0.3.22 Windows regression). Keep all useState/useRef above the derived consts.",
|
|
);
|
|
});
|