Milestone v1 (v2.0.0): Mokosh — Session Capture #1
@@ -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.<api>.<method>(...)` 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)
|
||||
Reference in New Issue
Block a user