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>
This commit is contained in:
2026-05-15 18:17:43 +02:00
parent 6aeeda495c
commit 1ebfb42b30
3 changed files with 321 additions and 11 deletions

View File

@@ -72,7 +72,7 @@ directory + `vite.config.ts` inline string + `src/background/`.
- [x] 01-03-PLAN.md — Offscreen recorder TDD: ring buffer + codec strict-mode + getDisplayMedia + track-ended cleanup; D-13 fallback skeleton pre-staged - [x] 01-03-PLAN.md — Offscreen recorder TDD: ring buffer + codec strict-mode + getDisplayMedia + track-ended cleanup; D-13 fallback skeleton pre-staged
- [x] 01-04-PLAN.md — Port keepalive + OFFSCREEN_READY handshake (TDD): replaces alarms keepalive on offscreen side - [x] 01-04-PLAN.md — Port keepalive + OFFSCREEN_READY handshake (TDD): replaces alarms keepalive on offscreen side
- [x] 01-05-PLAN.md — SW shrink: delete legacy buffer + alarms + IndexedDB + tabCapture paths; wire SW-side onConnect host - [x] 01-05-PLAN.md — SW shrink: delete legacy buffer + alarms + IndexedDB + tabCapture paths; wire SW-side onConnect host
- [ ] 01-06-PLAN.md — Build pipeline collapse: delete vite.config.ts inline plugin + top-level offscreen/ dir; declare rollupOptions.input - [x] 01-06-PLAN.md — Build pipeline collapse: delete vite.config.ts inline plugin + top-level offscreen/ dir; declare rollupOptions.input
- [ ] 01-07-PLAN.md — Manual smoke + ffprobe D-12 acceptance gate; commit regression fixture - [ ] 01-07-PLAN.md — Manual smoke + ffprobe D-12 acceptance gate; commit regression fixture
### Phase 2: Stabilize DOM + event capture privacy ### Phase 2: Stabilize DOM + event capture privacy
@@ -224,7 +224,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5.
| Phase | Plans Complete | Status | Completed | | Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------| |-------|----------------|--------|-----------|
| 1. Stabilize video pipeline | 4/7 | In Progress| | | 1. Stabilize video pipeline | 6/7 | In Progress| |
| 2. Stabilize DOM + event capture privacy | 0/TBD | Not started | - | | 2. Stabilize DOM + event capture privacy | 0/TBD | Not started | - |
| 3. Stabilize export pipeline | 0/TBD | Not started | - | | 3. Stabilize export pipeline | 0/TBD | Not started | - |
| 4. SPEC §10 smoke verification | 0/TBD | Not started | - | | 4. SPEC §10 smoke verification | 0/TBD | Not started | - |

View File

@@ -3,15 +3,15 @@ gsd_state_version: 1.0
milestone: v2.0.0 milestone: v2.0.0
milestone_name: milestone milestone_name: milestone
status: executing status: executing
stopped_at: Completed Plan 01-05SW shrink + onConnect host wired (T-1-04 sender check, OFFSCREEN_READY handshake, port-based buffer fetch, IDB orphan cleanup, hasDocument re-sync); 9/9 tests still green; Plan 06 next (vite.config.ts collapse) stopped_at: Completed Plan 01-06vite.config.ts collapse (226 -> 21 lines), orphan offscreen/ dir deleted, dist build green, crxjs Outcome A confirmed and SW URL reconciled (chrome.runtime.getURL('src/offscreen/index.html')); 9/9 tests still green; Plan 07 next (ffprobe acceptance gate)
last_updated: "2026-05-15T16:06:29.434Z" last_updated: "2026-05-15T16:16:50.760Z"
last_activity: 2026-05-15 last_activity: 2026-05-15
progress: progress:
total_phases: 5 total_phases: 5
completed_phases: 0 completed_phases: 0
total_plans: 7 total_plans: 7
completed_plans: 5 completed_plans: 6
percent: 71 percent: 86
--- ---
# Project State # Project State
@@ -28,12 +28,12 @@ no server, no password leaks.
## Current Position ## Current Position
Phase: 1 (Stabilize Video Pipeline) — EXECUTING Phase: 1 (Stabilize Video Pipeline) — EXECUTING
Plan: 6 of 7 Plan: 7 of 7
Status: Ready to execute Status: Ready to execute
Last activity: 2026-05-15 Last activity: 2026-05-15
REQUIREMENTS.md, ROADMAP.md, STATE.md written) REQUIREMENTS.md, ROADMAP.md, STATE.md written)
Progress: [███████░░░] 71% Progress: [█████████░] 86%
## Performance Metrics ## Performance Metrics
@@ -64,6 +64,7 @@ Progress: [███████░░░] 71%
| Phase 1 P03 | 8min | 3 tasks | 5 files | | Phase 1 P03 | 8min | 3 tasks | 5 files |
| Phase 01 P04 | 4min | 3 tasks | 1 files | | Phase 01 P04 | 4min | 3 tasks | 1 files |
| Phase 01 P05 | 8min | 2 tasks | 1 files | | Phase 01 P05 | 8min | 2 tasks | 1 files |
| Phase 1 P06 | 3min | 2 tasks | 2 files |
## Accumulated Context ## Accumulated Context
@@ -98,6 +99,8 @@ current work:
- [Phase ?]: [Phase 01-05]: Added chrome.offscreen.hasDocument() in initialize() — Rule 2 robustness, audit P1 #8 mitigation across SW respawns - [Phase ?]: [Phase 01-05]: Added chrome.offscreen.hasDocument() in initialize() — Rule 2 robustness, audit P1 #8 mitigation across SW respawns
- [Phase ?]: [Phase 01-05]: SW is now a pure coordinator — onConnect host bound to 'video-keepalive' port with T-1-04 sender check; getVideoBufferFromOffscreen replaces synchronous SW-local buffer fetch; OFFSCREEN_READY handshake closes the audit P1 #12 race - [Phase ?]: [Phase 01-05]: SW is now a pure coordinator — onConnect host bound to 'video-keepalive' port with T-1-04 sender check; getVideoBufferFromOffscreen replaces synchronous SW-local buffer fetch; OFFSCREEN_READY handshake closes the audit P1 #12 race
- [Phase ?]: [Phase 01-05]: indexedDB.deleteDatabase('VideoRecorderDB') in onInstalled — T-1-NEW-05-02 / RESEARCH.md Runtime State Inventory cleanup of orphaned IDB from pre-Phase-01 builds - [Phase ?]: [Phase 01-05]: indexedDB.deleteDatabase('VideoRecorderDB') in onInstalled — T-1-NEW-05-02 / RESEARCH.md Runtime State Inventory cleanup of orphaned IDB from pre-Phase-01 builds
- [Phase ?]: [Phase 01-06]: Collapsed vite.config.ts from 226 -> 21 lines (RESEARCH.md Example B verbatim); deleted 174-line inline copy-offscreen plugin (audit P0 #1 root cause) and the orphan offscreen/ top-level directory (D-08)
- [Phase ?]: [Phase 01-06]: crxjs Outcome A confirmed — dist/src/offscreen/index.html (preserves src/ prefix from rollupOptions.input key). SW URL adjusted to chrome.runtime.getURL('src/offscreen/index.html'); RESEARCH.md Pitfall 5 binding empirically verified
### Pending Todos ### Pending Todos
@@ -120,7 +123,7 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity ## Session Continuity
Last session: 2026-05-15T16:06:29.412Z Last session: 2026-05-15T16:16:50.743Z
Stopped at: Completed Plan 01-05SW shrink + onConnect host wired (T-1-04 sender check, OFFSCREEN_READY handshake, port-based buffer fetch, IDB orphan cleanup, hasDocument re-sync); 9/9 tests still green; Plan 06 next (vite.config.ts collapse) Stopped at: Completed Plan 01-06vite.config.ts collapse (226 -> 21 lines), orphan offscreen/ dir deleted, dist build green, crxjs Outcome A confirmed and SW URL reconciled (chrome.runtime.getURL('src/offscreen/index.html')); 9/9 tests still green; Plan 07 next (ffprobe acceptance gate)
intel synthesis. Coverage validated: 11/11 v1 REQs mapped. intel synthesis. Coverage validated: 11/11 v1 REQs mapped.
Resume file: .planning/phases/01-stabilize-video-pipeline/01-06-PLAN.md Resume file: None

View File

@@ -0,0 +1,307 @@
---
phase: 01-stabilize-video-pipeline
plan: 06
subsystem: build-pipeline
tags: [vite, crxjs, rollup-input, mv3, offscreen-document, dead-code-deletion, build-config, p0-1, audit-p2-17, audit-p2-18, d-07, d-08]
# Dependency graph
requires:
- phase: 01-stabilize-video-pipeline
provides: "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"
provides:
- "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."
affects: [01-07-ffprobe-gate]
# Tech tracking
tech-stack:
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)."
key-files:
created: []
modified:
- "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)"
deleted:
- "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)"
key-decisions:
- "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."
patterns-established:
- "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."
requirements-completed: [] # REQ-video-ring-buffer remains pending Plan 07 ffprobe gate
# Metrics
duration: 3min
completed: 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`:**
```typescript
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:
```diff
- 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.json``desktopCapture` 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: \`<JS-as-string>\` })` calls is deleted. Grep gate `grep -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