Files
Mark 1ebfb42b30 docs(01-06): complete vite.config.ts collapse plan
- 01-06-SUMMARY.md: detailed write-up — 226 → 21 lines, Outcome A
  reconciliation (dist/src/offscreen/index.html), full dist layout
  for Plan 07's smoke test, T-1-NEW-06-01 / T-1-NEW-06-02 grep gates
- STATE.md: completed_plans 5 → 6, percent 71 → 86, current plan
  advanced 6 → 7, two new decisions logged, session stopped_at updated
- ROADMAP.md: Phase 1 plan progress row 4/7 → 6/7; 01-06-PLAN.md
  checked off

REQ-video-ring-buffer remains unchecked — Plan 07 owns the ffprobe gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:17:43 +02:00

17 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
01-stabilize-video-pipeline 06 build-pipeline
vite
crxjs
rollup-input
mv3
offscreen-document
dead-code-deletion
build-config
p0-1
audit-p2-17
audit-p2-18
d-07
d-08
phase provides
01-stabilize-video-pipeline Plan 03 created src/offscreen/index.html + src/offscreen/recorder.ts (the crxjs-managed entry that replaces the orphan offscreen/ dir); Plan 05 left chrome.runtime.getURL('offscreen/index.html') in src/background/index.ts:45 for Plan 06 to reconcile against the actual crxjs emit path
Collapsed vite.config.ts: 226 → 21 lines (-205). The 174-line inline copy-offscreen plugin (audit P0 #1 root cause) is GONE.
Orphan offscreen/ top-level directory deleted (offscreen/index.ts + offscreen/index.html — both dead per D-08).
rollupOptions.input.offscreen wired to src/offscreen/index.html (RESEARCH.md Example B); crxjs picks up the recorder.ts module via the HTML <script> reference.
Outcome A confirmed: crxjs PRESERVES the src/ prefix in the bundled output — dist/src/offscreen/index.html, NOT dist/offscreen/index.html.
src/background/index.ts line 45 reconciled: chrome.runtime.getURL('src/offscreen/index.html') now matches the actual emit path (RESEARCH.md Pitfall 5 binding).
Clean dist/ layout: manifest.json + service-worker-loader.js + 7 hashed assets/*.js bundles + bundled HTML for offscreen and popup + icons.
01-07-ffprobe-gate
added patterns
Minimal vite config pattern: crx() + rollupOptions.input — the canonical crxjs MV3 idiom (discussions #919 and #1060).
Build-time path binding: the SW's chrome.runtime.getURL string MUST equal the rollup input key path (Pitfall 5). Verified empirically post-build, not inferred.
Build-config attack-surface reduction: deleting an inline plugin that called this.emitFile with a stringified JS payload removes a string-injection vector (T-1-NEW-06-01).
created modified deleted
vite.config.ts (226 → 21 lines; the entire inline copy-offscreen plugin block, the misplaced publicDir+copyPublicDir keys, and the manualChunks:undefined shape are all gone; replaced with RESEARCH.md Example B verbatim)
src/background/index.ts:45 (1-line change; getURL('offscreen/index.html') → getURL('src/offscreen/index.html') to match crxjs Outcome-A emit path)
offscreen/index.ts (60 lines of dead tabCapture-era recorder; replaced by src/offscreen/recorder.ts per D-07 / D-08)
offscreen/index.html (10 lines; replaced by src/offscreen/index.html created by Plan 03)
offscreen/ top-level directory (now empty; rmdir'd)
Outcome A obtained: crxjs preserves the 'src/' prefix from the rollupOptions.input key. dist/src/offscreen/index.html is the emitted path, not dist/offscreen/index.html. RESEARCH.md Pitfall 5 documented both possibilities; the runtime verification settled it. SW URL string was updated accordingly (1-line fix in src/background/index.ts:45).
No path adjustment via 'leaving the SW URL unchanged' (Outcome B) was used — that would have required some non-crxjs build trick to strip the src/ prefix, and there's no benefit. Outcome A is the canonical crxjs behavior; matching the SW URL to it is the simplest correct path.
Followed the plan's verbatim Example-B vite.config.ts (with double quotes per the plan body, which differ from the codebase's prior single-quote style — but the plan body specifies double quotes, and Prettier hasn't yet established a project-wide rule, so verbatim was preferred over style-rewrap).
Did NOT mark REQ-video-ring-buffer complete: per the prompt and Plan 05's precedent, Plan 07's ffprobe gate is the requirement-satisfaction proof.
crxjs entry pattern: HTML in rollupOptions.input → crxjs binds the HTML's <script type=module> ref → bundled output preserves the input key path. The SW resolves the runtime URL via chrome.runtime.getURL(<same-key>).
Two-step verification flow for any future build-config change: (1) edit vite.config.ts, (2) rm -rf dist && npm run build, (3) inspect dist/ for the actual emit paths, (4) reconcile any SW/manifest references that hard-code paths. This avoids the Pitfall 5 class of 'works locally because no one ran the build' bugs.
3min 2026-05-15

Phase 01 Plan 06: Vite Config Collapse Summary

Collapsed vite.config.ts from 226 lines to 21 by deleting the 174-line inline copy-offscreen plugin (audit P0 #1) and the dead offscreen/ top-level directory; wired the new src/offscreen/index.html entry via rollupOptions.input; verified the crxjs emit path against the SW's chrome.runtime.getURL(...) string (Pitfall 5) and adjusted the SW URL to src/offscreen/index.html to match Outcome A.

Performance

  • Duration: ~3 min
  • Started: 2026-05-15T16:09:24Z
  • Completed: 2026-05-15T16:11:30Z
  • Tasks: 2 (vite.config.ts rewrite + dead-code deletion; build verification + path reconciliation)
  • Commits: 2 (one per task)
  • Files modified: 2 (vite.config.ts, src/background/index.ts)
  • Files deleted: 2 (offscreen/index.ts, offscreen/index.html) + 1 dir (offscreen/)

Before / After

Snapshot vite.config.ts lines Notes
Pre-Plan 06 226 Inline copy-offscreen plugin (lines 13-216) + misplaced publicDir/copyPublicDir + manualChunks:undefined shape.
Post-Task 1 21 RESEARCH.md Example B verbatim — crx() + rollupOptions.input.offscreen. The plan's wc -l vite.config.ts ≤ 30 ceiling is comfortably met.
$ find offscreen/ 2>&1 || echo absent
bfs: error: offscreen/: No such file or directory.
absent

Task 1: vite.config.ts rewrite + offscreen/ deletion

Commit: 23e69d0 refactor(01-06): delete inline copy-offscreen plugin and orphan offscreen/ directory

Deletions:

  • offscreen/index.ts (60 lines — orphan dead-code from tabCapture era; D-08 explicit DELETE target; audit P2 #18)
  • offscreen/index.html (10 lines — replaced by src/offscreen/index.html per D-07 / Plan 03)
  • offscreen/ directory (rmdir after the two file deletes; verified empty first)
  • vite.config.ts inline copy-offscreen plugin (174 lines, lines 13-216): the this.emitFile-based emitter for both the offscreen HTML and the stringified JS module that ran the tabCapture-era chromeMediaSource + IndexedDB recording. T-1-NEW-06-01 mitigation.
  • vite.config.ts misplaced publicDir: 'public' / copyPublicDir: true (audit P2 #17 — there is no public/ directory in this repo)
  • vite.config.ts rollupOptions.output.manualChunks: undefined (no longer needed; crxjs handles chunking)

Replacement — final vite.config.ts:

import { defineConfig } from 'vite';
import { crx } from '@crxjs/vite-plugin';
import manifest from './manifest.json';

export default defineConfig({
  plugins: [
    crx({
      manifest,
      contentScripts: {
        injectCss: false,
      },
    }),
  ],
  build: {
    rollupOptions: {
      input: {
        offscreen: 'src/offscreen/index.html',
      },
    },
  },
});

Acceptance gates (all PASS):

Gate Expected Actual
test ! -f offscreen/index.ts exit 0 PASS
test ! -f offscreen/index.html exit 0 PASS
test ! -d offscreen exit 0 PASS
grep -c "copy-offscreen" vite.config.ts 0 0
grep -c "this.emitFile" vite.config.ts 0 0 (T-1-NEW-06-01 grep gate)
grep -c "VideoRecorderDB" vite.config.ts 0 0
grep -c "openIndexedDB" vite.config.ts 0 0
grep -c "chromeMediaSource" vite.config.ts 0 0
grep -c "rollupOptions" vite.config.ts 1 1
grep -c "src/offscreen/index.html" vite.config.ts 1 1
wc -l vite.config.ts ≤ 30 21
npx tsc --noEmit exit 0 PASS
npx vitest run 9/9 PASS 9/9 PASS

Task 2: Build verification + offscreen URL reconciliation

Commit: 6aeeda4 fix(01-06): align ensureOffscreen URL with crxjs emit path

npm run build output (excerpt)

> ai-call-extension@1.0.0 build
> tsc && vite build

vite v5.4.21 building for production...
transforming...
✓ 59 modules transformed.
rendering chunks...
computing gzip size...
dist/service-worker-loader.js                     0.04 kB
dist/icons/icon16.png                             0.08 kB
dist/icons/icon48.png                             0.12 kB
dist/icons/icon128.png                            0.31 kB
dist/assets/index.ts-loader-BkeIfRno.js           0.34 kB
dist/src/offscreen/index.html                     0.39 kB │ gzip:  0.23 kB
dist/src/popup/index.html                         0.76 kB │ gzip:  0.51 kB
dist/manifest.json                                1.14 kB │ gzip:  0.55 kB
dist/assets/index-BXJP9H5s.css                    1.08 kB │ gzip:  0.54 kB
dist/assets/modulepreload-polyfill-B5Qt9EMX.js    0.71 kB │ gzip:  0.40 kB
dist/assets/logger-D8-HRtHy.js                    1.04 kB │ gzip:  0.32 kB
dist/assets/index.html-BR0hR1fK.js                2.27 kB │ gzip:  1.04 kB
dist/assets/offscreen-SqX0ET3Q.js                 3.03 kB │ gzip:  1.42 kB
dist/assets/index.ts-BcLq5y3Q.js                 73.79 kB │ gzip: 24.04 kB
dist/assets/index.ts-D-8Ku9Ua.js                104.47 kB │ gzip: 32.91 kB
✓ built in 1.09s

find dist -type f | sort — full layout for Plan 07's manual smoke load

dist/assets/index-BXJP9H5s.css
dist/assets/index.html-BR0hR1fK.js
dist/assets/index.ts-BcLq5y3Q.js
dist/assets/index.ts-D-8Ku9Ua.js
dist/assets/index.ts-loader-BkeIfRno.js
dist/assets/logger-D8-HRtHy.js
dist/assets/modulepreload-polyfill-B5Qt9EMX.js
dist/assets/offscreen-SqX0ET3Q.js
dist/icons/icon128.png
dist/icons/icon16.png
dist/icons/icon48.png
dist/manifest.json
dist/service-worker-loader.js
dist/src/offscreen/index.html
dist/src/popup/index.html

Outcome identification — RESEARCH.md Pitfall 5

The bundled offscreen HTML lands at dist/src/offscreen/index.html (crxjs preserved the src/ prefix from the rollup input key). dist/offscreen/index.html does NOT exist. This is Outcome A per the plan's dichotomy.

SW URL adjustment: Plan 05 left chrome.runtime.getURL('offscreen/index.html') at src/background/index.ts:45. Under Outcome A this would have produced ERR_FILE_NOT_FOUND at chrome.offscreen.createDocument time and broken Plan 07's manual smoke load. The one-line edit:

-    const url = chrome.runtime.getURL('offscreen/index.html');
+    const url = chrome.runtime.getURL('src/offscreen/index.html');

The committed SW URL string and the actual dist/ emit path now match — verified empirically, not inferred.

dist/manifest.json permissions verification

$ node -e "const m=require('./dist/manifest.json'); const p=m.permissions; \
    console.log('Has desktopCapture:', p.includes('desktopCapture')); \
    console.log('Has tabCapture:', p.includes('tabCapture'));"

Has desktopCapture: true
Has tabCapture: false

The post-D-A6 manifest amendment propagated correctly through crxjs into the bundled dist/manifest.jsondesktopCapture present, tabCapture absent, alarms absent (already removed by Plan 05's manifest cleanup).

Acceptance gates (all PASS)

Gate Expected Actual
npm run build exit code 0 0
dist/manifest.json exists YES YES
dist/src/offscreen/index.html OR dist/offscreen/index.html one of two dist/src/offscreen/index.html (Outcome A)
dist/manifest.json permissions contains desktopCapture YES YES
dist/manifest.json permissions contains tabCapture NO NO
SW chrome.runtime.getURL(...) string matches dist emit YES YES (src/offscreen/index.html)
dist/assets/ JS file count ≥ 1 7
npx tsc --noEmit post-edit exit 0 PASS
npx vitest run post-edit 9/9 PASS 9/9 PASS

Deviations from Plan

Rule 1 — Bug fix (auto-applied, paired with Task 2)

1. [Rule 1 - Bug] SW URL chrome.runtime.getURL('offscreen/index.html') would have 404'd against the Outcome-A dist layout

  • Found during: Task 2 (the planned post-build path reconciliation — this is the exact scenario RESEARCH.md Pitfall 5 calls out).
  • Issue: Plan 05's executor left the pre-amendment string 'offscreen/index.html' at src/background/index.ts:45. Once Plan 06 wired rollupOptions.input.offscreen = 'src/offscreen/index.html' and crxjs preserved the src/ prefix in the emit, the SW URL no longer pointed at a real file. chrome.offscreen.createDocument would have returned ERR_FILE_NOT_FOUND. Plan 05's executor explicitly flagged this as the Task-2 runtime verification of Plan 06 (citation: STATE.md decisions block).
  • Fix: One-line edit — 'offscreen/index.html''src/offscreen/index.html'.
  • Files modified: src/background/index.ts:45.
  • Verification: Post-edit npm run build still exits 0; the committed string is chrome.runtime.getURL('src/offscreen/index.html') and dist/src/offscreen/index.html exists.
  • Committed in: 6aeeda4 (Task 2 commit).

This was foreseen by the plan body itself (it explicitly describes the A/B outcome dichotomy and prescribes the edit conditional on which outcome obtains). It is a deviation only against the literal "Task 2 may need no commit if no path adjustment was needed" branch — Outcome A required the adjustment, so Task 2 produced its own commit. Not a true deviation in spirit; logged here for traceability.


Total deviations: 1 auto-fixed (Rule 1 — path binding). Impact on plan: Zero scope creep. The plan body anticipated exactly this fix and put it inline as Task 2 step (4). The line edit was load-bearing for Plan 07's manual smoke load.

Issues Encountered

None. Build was clean on first run. Tests stayed at 9/9 throughout. No tsc errors. No regressions in Plan 04's port-based test suite or Plan 02 / Plan 03's ring-buffer + codec-check suites.

TDD Gate Compliance

This plan was type: execute (not type: tdd). No RED/GREEN/REFACTOR cycle required. The existing test suite served as a regression guard — npx vitest run returned 9/9 PASS both at the start of the plan and after each task commit.

Authentication Gates

None. Pure build-pipeline refactor.

Known Stubs

None. The replacement vite.config.ts is complete and functional — npm run build produces a loadable extension. No placeholder/TODO/coming-soon markers introduced.

Threat Flags

None. Threat surface is REDUCED, not expanded:

  • T-1-NEW-06-01 (string-injected code via inline plugin): MITIGATED — the entire inline plugin with its this.emitFile({ source: \` })calls is deleted. Grep gategrep -c "this.emitFile" vite.config.ts` returns 0.
  • T-1-NEW-06-02 (orphaned root-level offscreen): MITIGATED — offscreen/ directory absent. The post-deletion find offscreen/ reports "No such file or directory" / "absent".

CLAUDE.md Compliance

  • No as any casts introduced in any modified file (vite.config.ts has no casts; the SW edit is a string literal change).
  • No @ts-ignore introduced.
  • vite.config.ts follows the canonical RESEARCH.md Example B form (lifted verbatim from a cited authoritative source — crxjs documentation + discussion #919).
  • No new constants needed to be moved; the replacement file is small enough that no extracted-constants refactor applies.

Next Plan Readiness — Plan 07

Plan 07 (the ffprobe acceptance gate per D-12) can now:

  1. Load dist/ unpacked into Chrome ≥ 116 (no further build needed; dist/manifest.json is the loadable extension).
  2. Trigger the recording from popup; the offscreen at dist/src/offscreen/index.html will be created by the SW's chrome.offscreen.createDocument({ url: 'chrome-extension://<id>/src/offscreen/index.html', ... }) call.
  3. Run the ffprobe acceptance gate against the exported last_30sec.webm.

The full dist/ listing is captured above so Plan 07's smoke test knows the exact files to expect.

Plan 07 loads dist/ unpacked into Chrome ≥ 116 and runs the ffprobe acceptance gate (D-12).

Self-Check

Verified after writing this summary:

  • vite.config.ts file exists: FOUND.
  • src/background/index.ts file exists: FOUND.
  • offscreen/ directory absent: FOUND (the absence itself was checked: find offscreen/ 2>&1 || echo absent → absent).
  • dist/manifest.json exists: FOUND.
  • dist/src/offscreen/index.html exists: FOUND.
  • Commit 23e69d0 exists in git log (Task 1).
  • Commit 6aeeda4 exists in git log (Task 2).
  • npx tsc --noEmit exits 0.
  • npx vitest run reports 9/9 PASS across 4 test files.
  • wc -l vite.config.ts returns 21 (≤ 30 plan ceiling).
  • grep -c "copy-offscreen\|chromeMediaSource\|this.emitFile" vite.config.ts returns 0.
  • SW URL string chrome.runtime.getURL('src/offscreen/index.html') matches Outcome-A emit path dist/src/offscreen/index.html.

Self-Check: PASSED