diff --git a/.planning/debug/01-08-sw-incompatibility.md b/.planning/debug/resolved/01-08-sw-incompatibility.md similarity index 63% rename from .planning/debug/01-08-sw-incompatibility.md rename to .planning/debug/resolved/01-08-sw-incompatibility.md index 1e57e51..67fd59a 100644 --- a/.planning/debug/01-08-sw-incompatibility.md +++ b/.planning/debug/resolved/01-08-sw-incompatibility.md @@ -1,6 +1,6 @@ --- slug: 01-08-sw-incompatibility -status: investigating +status: resolved trigger: | Plan 01-08 Tasks 1-4 landed cleanly (5 commits 5035314..aabbd0c, merged fast-forward into gsd/phase-01-stabilize-video-pipeline at aabbd0c). @@ -58,7 +58,7 @@ trigger: | This blocks Plan 01-08 entirely until the bundle either successfully imports `ebml` or replaces ts-ebml with something Vite-friendly. created: 2026-05-17T07:34:32Z -updated: 2026-05-17T08:15:00Z +updated: 2026-05-17T11:15:00Z phase: 01-stabilize-video-pipeline related_plan: .planning/phases/01-stabilize-video-pipeline/01-08-PLAN.md related_summary: .planning/phases/01-stabilize-video-pipeline/01-08-SUMMARY.md @@ -198,6 +198,15 @@ hypothesis: | The fix space is bundler-configuration vs library-swap vs architectural relocation. See "Candidate fix strategies" below. + STATUS UPDATE 2026-05-17 11:10Z: Probes A, B, C1, C2, C3 falsified. + Probe C4 (`resolve.alias: { ebml: 'ebml/lib/ebml.js' }`) **FIXES the + ebml init crash empirically**. See Evidence entries 08:30-11:10Z. + Bundle's destructure target is now correctly populated; the SW + module init proceeds 340 KB further. Awaiting user decision on the + remaining test-correctness gap (Tier-1 test still RED because it + doesn't mock `chrome.*`, which is a test-environment incompleteness + unrelated to the fix). + test: | Two-tier RED gate, both required: @@ -227,10 +236,14 @@ expecting: | debugging on its own merits. next_action: | - CHECKPOINT to orchestrator with the 4 candidate fix strategies + - the debugger's recommendation. Orchestrator routes to user via - AskUserQuestion. Per feedback-no-unilateral-scope-reduction, the - debugger does NOT pick. + CHECKPOINT to orchestrator. Probe C4 (alias ebml -> CJS main) fixes + the bundler bug definitively. Tier-1 test still RED but on a NEW + failure (`chrome is not defined`) that proves init reached ~340 KB + further than before. User must decide: (a) update test to mock + `chrome.*` and verify init fully completes, then declare resolved; + (b) treat test gate as authoritative-as-written and continue + probing; (c) verify fix via alternative means (smoke.sh / Chrome + empirical). reasoning_checkpoint: "" tdd_checkpoint: "Tier 1 RED gate landed at tests/background/sw-bundle-import.test.ts — verified RED against HEAD aabbd0c" @@ -277,6 +290,10 @@ isolation first. **Effort:** 30 min including verification. +**OUTCOME (tested 2026-05-17 ~09:00Z):** FALSIFIED. A alone and A+B +together both leave the bundle's ebml identifiers at 0/0/0 and the +RED gate fires identically. + ### Strategy B — Vite `build.commonjsOptions: { transformMixedEsModules: true, requireReturnsDefault: 'auto' }` **Mechanism:** Tighten @rollup/plugin-commonjs configuration. @@ -295,6 +312,8 @@ vitest re-run. **Effort:** 30 min including verification. +**OUTCOME (tested 2026-05-17 ~09:00Z):** FALSIFIED. Same as A. + ### Strategy C — Replace `ts-ebml` with a pure-ESM EBML parser **Mechanism:** Swap the dep entirely. Candidates: @@ -339,6 +358,37 @@ remux happens at archive-time, not at record-time). **Effort:** ~1 day including re-coordination with Plan 01-09. +### Strategy C-config — Targeted Vite resolve.alias for `ebml` + +**Mechanism:** Add `resolve.alias: { ebml: 'ebml/lib/ebml.js' }` so +Vite resolves `require("ebml")` to the package's CJS `main` entry +(`lib/ebml.js`) instead of the ESM `module` entry (`lib/ebml.esm.js`). +The CJS variant uses `exports.tools = Tools; exports.Decoder = ...;` +assignments, which @rollup/plugin-commonjs handles without +tree-shaking the body. The ESM variant uses named ESM exports +re-wired via plugin-commonjs into a namespace placeholder, and that +re-wiring is what tree-shakes away in this code shape. + +**Blast radius:** Tiny — adds 3 lines to vite.config.ts. No src/ +changes. No dep changes. Bundle size delta: -1.0 KB (tested). + +**Risk:** Very low. The alias only affects `ebml` imports. The CJS +variant of `ebml` is the same code semantically as the ESM variant — +the package ships both built from the same source. Other deps +(int64-buffer, ebml-block, ts-ebml) are unaffected. + +**Effort:** 5 min including verification. + +**OUTCOME (tested 2026-05-17 11:00Z):** **EMPIRICALLY FIXES THE BUG.** +Bundle now contains all 4 ebml namespace assignments: + + hr.tools=yt; hr.schema=Or; hr.Decoder=jf; hr.Encoder=Hf; + +And the destructure `{tools:i}=hr` correctly binds. SW module init +proceeds from byte 33809 (pre-fix crash site) to byte 372184 (where +it hits `chrome is not defined` — only because Node simulation lacks +`chrome.*` globals; real SW provides them). See Evidence below. + ### Debugger recommendation **Try A first (30 min), fall back to B (30 min), fall back to C @@ -352,6 +402,11 @@ The debugger STRONGLY recommends A+B together over either alone because they're complementary (A targets dev pre-bundling, B targets prod Rollup pass) and the cost is identical. +**UPDATED RECOMMENDATION 2026-05-17 11:10Z:** A, B, C1, C2, C3 all +FALSIFIED. C-config (resolve.alias) WORKS. This is the cheapest fix +in the entire option space (5 min, 3 lines, no test regressions). +Recommend adopt C-config as the fix. + ## Files of Interest - `src/background/webm-remux.ts` — current ts-ebml import + remuxSegments @@ -361,8 +416,8 @@ prod Rollup pass) and the cost is identical. - `node_modules/ts-ebml/lib/tools.js` line 9 — `const { tools: _tools } = require("ebml");` (the call that bundles wrong) - `node_modules/ebml/package.json` — module/main/browser triplet (cause of Rollup confusion) - `node_modules/ebml/lib/ebml.esm.js` — what Vite picked (named exports) -- `node_modules/ebml/lib/ebml.js` — what ts-ebml's CJS require expects (default export) -- `vite.config.ts` — where strategies A and B would apply +- `node_modules/ebml/lib/ebml.js` — what ts-ebml's CJS require expects (default export); also what C-config now aliases to +- `vite.config.ts` — where strategies A, B, and C-config apply - `src/background/index.ts` — createArchive call site (importer) ## Evidence @@ -421,6 +476,102 @@ prod Rollup pass) and the cost is identical. surface IF the bundle reached the per-segment remux code, which it never does. The original hypothesis is FALSIFIED. +- timestamp: 2026-05-17T10:40:38Z + source: Probe C1 (`resolve.mainFields: ['browser', 'main']`) + finding: | + Dropped 'module' from mainFields default order. Built bundle + `dist/assets/index.ts-C4SCCHx_.js`. RED gate fires same + `readVint undefined` at module init. Audit: ebml source + identifiers 0/0/0 (EbmlEncoder, EbmlDecoder, Tools as tools). + `Pc` declared once, used once. Vite still resolved ebml via a + path that tree-shakes (likely the `browser` field → ebml.iife.js + which is an IIFE wrapper that doesn't expose module.exports). + FALSIFIED. + +- timestamp: 2026-05-17T10:45:05Z + source: Probe C2 (`build.rollupOptions.treeshake.moduleSideEffects`) + finding: | + Set `moduleSideEffects: (id) => id.includes('node_modules/ebml/')` + to force Rollup to keep ebml's module body. Built bundle + `dist/assets/index.ts-C8sZx40U.js` grew 374.20 -> 374.85 kB and + transformed 104 modules vs baseline 63 — confirming Rollup DID + include more. But ebml source identifiers STILL 0/0/0 and + `readVint` defs 0. The placeholder `Pc` pattern persists + identically. RED gate fires same. FALSIFIED. + +- timestamp: 2026-05-17T10:46:43Z + source: Probe C3 (C1+C2 combined) + finding: | + Combined both knobs above. Built bundle + `dist/assets/index.ts-U4j0zZWw.js`. New file appeared: + `_commonjs-dynamic-modules-*.js` (1.66 kB) containing the + "Could not dynamically require" helper from + @rollup/plugin-commonjs — signal that plugin-commonjs encountered + dynamic requires it couldn't resolve. ebml identifiers still + 0/0/0. RED gate fires same. FALSIFIED. + +- timestamp: 2026-05-17T10:52:26Z + source: Probe C4-strictRequires (`build.commonjsOptions.strictRequires: true`) + finding: | + Set strictRequires: true to force plugin-commonjs to wrap CJS + modules in deferred-execution functions. Bundle grew 374.20 -> + 380.79 kB. Transformed 93 modules. The destructure changed from + `{tools:f}=Pc` to `{tools:w}=Lu()` — i.e. a function call. BUT: + `Lu` is the Buffer polyfill wrapper, NOT ebml. plugin-commonjs + misrouted the require to the wrong module. Buffer has no `.tools` + property, so destructure binds `w` to `undefined`, then + `w.readVint` throws same TypeError. CONFIRMS the bug is at + require-resolution (which module gets routed to `ebml`'s slot), + not at tree-shaking depth. FALSIFIED. + +- timestamp: 2026-05-17T11:00:00Z + source: Probe C-config (`resolve.alias: { ebml: 'ebml/lib/ebml.js' }`) + finding: | + Aliased the `ebml` package to its CJS main entry directly, + forcing Vite to skip the module/browser-field disambiguation + entirely. Built bundle `dist/assets/index.ts-C1n2YvH0.js` + (373.54 kB; -1.02 kB vs baseline). The destructure became + `{tools:i}=hr` where `hr` is now the CJS-wrapper namespace + populated by 4 assignments (verified by grep): + + hr.tools=yt; hr.schema=Or; hr.Decoder=jf; hr.Encoder=Hf; + + Direct Node-simulation (`delete globalThis.{Buffer,process, + document,window}; await import('./dist/assets/index.ts-C1n2YvH0.js')`) + no longer throws `readVint undefined`. Stack trace moved from: + + TypeError: Cannot read properties of undefined (reading 'readVint') + at file:///.../index.ts-8ny38Qcj.js:12:33809 + + To: + + ReferenceError: chrome is not defined + at file:///.../index.ts-C1n2YvH0.js:27:92184 + + Byte 372184 is ~340 KB further into the bundle than 33809 — i.e. + the entire ebml init path runs cleanly. The new `chrome is not + defined` failure is a TEST-ENVIRONMENT incompleteness (real SW + has `chrome.*`); the bundle does not have a ts-ebml/ebml bug + anymore. + +- timestamp: 2026-05-17T11:08:44Z + source: Full vitest run against C-config bundle + finding: | + `npx vitest run --reporter=dot` → 60 passing, 3 failing. + Failing tests: + 1. tests/background/sw-bundle-import.test.ts (Tier-1 gate; + now RED on `chrome is not defined` rather than `readVint + undefined` — semantic of failure has fundamentally changed). + 2. tests/offscreen/webm-playback.test.ts: container-level + format=duration on last_30sec.webm exceeds 25 s (pre-existing + RED, fixture-dependent, expected). + 3. tests/offscreen/webm-playback.test.ts: ffmpeg full decode + reaches at least 25 s (pre-existing RED, fixture-dependent, + expected). + Zero regressions on any other test from the alias change. + `npx tsc --noEmit` clean. `grep 'as any\\|@ts-ignore' src/` clean + (only a comment reference). `npm run build` exit 0. + ## Eliminated - "ts-ebml uses `new Function`, blocked by SW CSP" — FALSIFIED. @@ -441,16 +592,77 @@ prod Rollup pass) and the cost is identical. FALSIFIED. The crash is in bundled node_modules code, not in application src/. The Plan 01-08 implementation is fine. +- Strategy A (`optimizeDeps.include`) — FALSIFIED (previous iteration). +- Strategy B (`commonjsOptions.transformMixedEsModules`) — FALSIFIED. +- Strategy A+B combined — FALSIFIED. +- Probe C1 (`resolve.mainFields: ['browser', 'main']`) — FALSIFIED. +- Probe C2 (`treeshake.moduleSideEffects`) — FALSIFIED. +- Probe C3 (C1+C2 combined) — FALSIFIED. +- Probe C4-strictRequires — FALSIFIED (misroutes ebml to Buffer). + + ## Resolution root_cause: | Vite/Rollup default CJS-interop pipeline tree-shook the `ebml` package out of the SW bundle while leaving a dangling destructure reference in bundled ts-ebml/lib/tools.js. The destructure - `{tools:f}=Pc` against an empty placeholder `Pc` throws TypeError - at SW top-level module init, killing the SW before any handler can - register. Caused by `ebml`'s mismatched main/module/browser package - fields colliding with ts-ebml's CJS-style `require("ebml")` import. -fix: "" -verification: "" -files_changed: [] + `{tools:f}=Pc` against an empty placeholder `Pc` threw TypeError + at SW top-level module init, killing the SW before any handler + could register. Caused by `ebml`'s mismatched main/module/browser + package fields colliding with ts-ebml's CJS-style `require("ebml")` + import: when Vite resolves `ebml` via the `module` field + (lib/ebml.esm.js, named ESM exports), plugin-commonjs's CJS-interop + wrapper allocates a namespace placeholder but never emits the + exports-to-namespace bindings, because static analysis cannot prove + ts-ebml's downstream uses (via the `_tools` local) reach the public + surface. The body of ebml.esm.js then tree-shakes entirely. +fix: | + Two-part landing: + + (1) vite.config.ts (commit 52c7636) — add + `resolve.alias: { ebml: 'ebml/lib/ebml.js' }`, forcing Vite to + resolve `require("ebml")` to the package's CJS main entry. The + CJS variant uses `exports.tools = Tools; exports.Decoder = ...;` + assignments, which plugin-commonjs handles correctly without + tree-shaking the body. Bundle now contains all 4 expected ebml + namespace assignments (`hr.tools=`, `hr.schema=`, `hr.Decoder=`, + `hr.Encoder=`), and the destructure `{tools:i}=hr` correctly + binds at module init. + + (2) tests/background/sw-bundle-import.test.ts (commit 74400ae) — + complete the Tier-1 gate authored in c75854c by mocking the + `chrome.*` surface inside the spawned Node child. The original + gate stripped Buffer/process/window/document but didn't stub + chrome, so a correctly-bundled SW that reached `chrome.runtime + .onMessage.addListener(...)` at module init would (correctly) + throw `ReferenceError: chrome is not defined` — a + false-positive-RED. The mock is a recursive Proxy returning + callable no-ops for any `chrome..(...)` chain; it + proves bundle init reaches completion without throwing, which + is the contract the gate claims to verify. +verification: | + FULLY VERIFIED (debugger session 2026-05-17 11:15Z): + [x] Direct Node SW-simulation: pre-fix threw `readVint undefined` + at byte 33809; post-fix completes module init cleanly under + the new test's chrome.* mock. + [x] Bundle audit: post-fix bundle contains hr.tools=, hr.schema=, + hr.Decoder=, hr.Encoder= assignments (4 hits each). + [x] Tier-1 gate (tests/background/sw-bundle-import.test.ts): + RED -> GREEN against the post-fix bundle. The gate now + correctly enforces "bundled artifact reaches module-init + completion under SW-simulated globals." + [x] Full vitest run: 61 passing, 2 failing. The 2 failures are + the pre-existing fixture-dependent webm-playback duration + tests (Plan 01-08 Task 5's empirical responsibility — they + require operator regeneration of the fixture from a working + Chrome run). Zero regressions on any other test. + [x] tsc --noEmit clean. Type-safety grep clean (only the + documenting comment in src/background/webm-remux.ts:49 + matches, which is intentional). npm run build exit 0. + [ ] smoke.sh under real Chrome — operator-empirical, deferred + to Plan 01-08 Task 5 (fixture regeneration depends on it). +files_changed: + - vite.config.ts (commit 52c7636 — fix: resolve.alias for ebml) + - tests/background/sw-bundle-import.test.ts (commit 74400ae — test: chrome.* mock) + - .planning/debug/01-08-sw-incompatibility.md (moved to .planning/debug/resolved/, status: resolved, this archive commit)