321 lines
15 KiB
Markdown
321 lines
15 KiB
Markdown
---
|
|
phase: 01-stabilize-video-pipeline
|
|
plan: 06
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["03"]
|
|
files_modified:
|
|
- vite.config.ts
|
|
- offscreen/index.ts
|
|
- offscreen/index.html
|
|
autonomous: true
|
|
requirements:
|
|
- REQ-video-ring-buffer
|
|
requirements_addressed:
|
|
- REQ-video-ring-buffer
|
|
|
|
must_haves:
|
|
truths:
|
|
- "`vite.config.ts` no longer contains the `copy-offscreen` inline plugin (the 200+-line block including IndexedDB plumbing, codec fallback chain, and `mediaRecorder` shadow is GONE)"
|
|
- "`vite.config.ts` declares the offscreen entry via `rollupOptions.input` per RESEARCH.md Example B"
|
|
- "Top-level `offscreen/index.ts` is DELETED (dead code per audit P2 #18)"
|
|
- "Top-level `offscreen/index.html` is DELETED (replaced by the crxjs-managed `src/offscreen/index.html` from Plan 03)"
|
|
- "`npm run build` exits 0; `dist/` contains a bundled offscreen HTML at the path the SW's `chrome.runtime.getURL` argument expects"
|
|
- "No mention of `VideoRecorderDB`, `openIndexedDB`, `chromeMediaSource`, or `copy-offscreen` remains in `vite.config.ts`"
|
|
artifacts:
|
|
- path: "vite.config.ts"
|
|
provides: "Minimal vite config with crxjs and offscreen entry"
|
|
min_lines: 15
|
|
contains: "rollupOptions"
|
|
- path: "dist/manifest.json"
|
|
provides: "Build output (artifact of npm run build)"
|
|
contains: "manifest_version"
|
|
- path: "dist/src/offscreen/index.html"
|
|
provides: "Bundled offscreen HTML at the path SW.ensureOffscreen expects (or wherever crxjs emits it — actual path verified at build time)"
|
|
contains: ""
|
|
key_links:
|
|
- from: "vite.config.ts (rollupOptions.input.offscreen)"
|
|
to: "src/offscreen/index.html"
|
|
via: "rollup input declaration"
|
|
pattern: "src/offscreen/index.html"
|
|
- from: "vite.config.ts"
|
|
to: "@crxjs/vite-plugin"
|
|
via: "crx({ manifest, contentScripts: { injectCss: false } })"
|
|
pattern: "crx("
|
|
---
|
|
|
|
<objective>
|
|
Collapse the build pipeline. DELETE the 200+-line `copy-offscreen` inline
|
|
Vite plugin block in `vite.config.ts` and the dead `offscreen/index.ts` +
|
|
`offscreen/index.html` at the repo root. The crxjs plugin already handles
|
|
bundling; we declare the new offscreen entry via `rollupOptions.input`
|
|
pointing at the `src/offscreen/index.html` Plan 03 created.
|
|
|
|
Purpose: D-08 says delete the inline plugin (which is the audit's P0 #1 root
|
|
cause). D-07 says crxjs picks up the new TS entry through the HTML
|
|
reference. The execution path is "minimal vite.config.ts + the existing
|
|
crx() invocation + a single rollupOptions.input line — that's it."
|
|
|
|
The plan also includes a build-time pathing verification — after the first
|
|
`npm run build`, the executor inspects `dist/` and confirms the bundled
|
|
offscreen HTML lands at the SAME path the SW's
|
|
`chrome.runtime.getURL(...)` argument in Plan 05 expects. If crxjs strips
|
|
the `src/` prefix (which it sometimes does), the SW URL gets adjusted by a
|
|
follow-up Edit. RESEARCH.md Pitfall 5 calls this out explicitly.
|
|
|
|
Output: A ~25-line `vite.config.ts`, no top-level `offscreen/` directory,
|
|
and a clean `npm run build` whose output passes the path-matching check.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md
|
|
@.planning/phases/01-stabilize-video-pipeline/01-RESEARCH.md
|
|
@.planning/phases/01-stabilize-video-pipeline/01-PATTERNS.md
|
|
@vite.config.ts
|
|
@offscreen/index.ts
|
|
@offscreen/index.html
|
|
@src/offscreen/index.html
|
|
@manifest.json
|
|
</context>
|
|
|
|
<interfaces>
|
|
crxjs entry contract (per RESEARCH.md §"Pitfall 5" and discussion #919):
|
|
- `rollupOptions.input` declares an HTML entry; crxjs bundles the HTML and emits its referenced TS module into `dist/assets/`.
|
|
- The runtime URL that the SW passes to `chrome.offscreen.createDocument` MUST match the bundled HTML path. If `input: { offscreen: 'src/offscreen/index.html' }`, the runtime URL is typically `chrome.runtime.getURL('src/offscreen/index.html')` (crxjs preserves the input key as the output path), but real-world projects have reported the `src/` prefix being stripped. The executor MUST verify after the first `npm run build` and adjust Plan 05's `ensureOffscreen` call if needed.
|
|
|
|
Plan 05's `ensureOffscreen` currently calls:
|
|
```typescript
|
|
const url = chrome.runtime.getURL('offscreen/index.html');
|
|
```
|
|
(this is leftover from the pre-amendment manifest entry that has the old root-level `offscreen/` dir). Plan 06 verifies the post-build emit path and updates this string to match. If the emit path is `src/offscreen/index.html`, the call becomes `getURL('src/offscreen/index.html')`. Plan 05 wrote the path AS-IS — Plan 06 is the right place to do the binding verification because it owns the build pipeline.
|
|
</interfaces>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| build-time vite plugin → bundled output | A malicious build plugin could inject code; we're DELETING our inline plugin (the audit's P0 #1) which is itself an attack-surface reduction |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-1-NEW-06-01 | Tampering — string-injected code via inline plugin | `vite.config.ts:13-216` `copy-offscreen` plugin's `this.emitFile({ source: \`<JS-as-string>\` })` | mitigate | DELETE the entire inline plugin. The replacement is a `src/offscreen/recorder.ts` real module + a `src/offscreen/index.html` declared in `rollupOptions.input`. No more long template-literal JS in `vite.config.ts`. Grep gate: `grep -v '^#' vite.config.ts \| grep -c "this.emitFile"` returns 0. |
|
|
| T-1-NEW-06-02 | Tampering — orphaned root-level offscreen | `offscreen/index.ts` + `offscreen/index.html` (dead code) | mitigate | DELETE both files. After this plan, a `find offscreen/ -type f` produces no output. Grep gate: `[ ! -d offscreen ]` exits 0. |
|
|
</threat_model>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: DELETE — top-level offscreen/ directory and inline copy-offscreen plugin</name>
|
|
<files>vite.config.ts, offscreen/index.ts, offscreen/index.html</files>
|
|
<read_first>
|
|
- vite.config.ts (lines 1-227 — full file; the inline plugin spans lines 13-216)
|
|
- offscreen/index.ts (60 lines — dead code)
|
|
- offscreen/index.html (10 lines — replaced by src/offscreen/index.html from Plan 03)
|
|
- .planning/phases/01-stabilize-video-pipeline/01-PATTERNS.md §`vite.config.ts` (lines 399-436)
|
|
- .planning/phases/01-stabilize-video-pipeline/01-RESEARCH.md §"Example B" (lines 895-915)
|
|
</read_first>
|
|
<action>
|
|
Three deletions and one rewrite.
|
|
|
|
(1) Delete `offscreen/index.ts` (the orphan dead-code file):
|
|
|
|
```bash
|
|
rm offscreen/index.ts
|
|
```
|
|
|
|
(2) Delete `offscreen/index.html` (it is REPLACED by the new `src/offscreen/index.html` that Plan 03 created — the path was deliberately moved into the source tree):
|
|
|
|
```bash
|
|
rm offscreen/index.html
|
|
```
|
|
|
|
(3) Remove the now-empty `offscreen/` directory (verify it is empty first; if anything else lives there, STOP and surface it):
|
|
|
|
```bash
|
|
[ -d offscreen ] && rmdir offscreen
|
|
```
|
|
|
|
(4) REWRITE `vite.config.ts` to be the minimal RESEARCH.md Example B form. Use the Write tool (NOT Edit — the file is being replaced). VERBATIM content:
|
|
|
|
```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',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
The deleted content (the entire `copy-offscreen` plugin block at lines 13-216 plus the surrounding `build.rollupOptions.output.manualChunks` shape at lines 218-226) does not survive any portion in the rewrite.
|
|
|
|
After these edits, run:
|
|
|
|
```bash
|
|
npx tsc --noEmit
|
|
```
|
|
|
|
It MUST exit 0 (the previous TS errors about `(error as any).message?.includes` etc. were inside `src/background/index.ts`; Plan 05 already cleared those. `vite.config.ts` is type-checked via Vite, not `tsc --noEmit`; the `npx tsc --noEmit` check here is a regression guard for the rest of the codebase).
|
|
</action>
|
|
<verify>
|
|
<automated>[ ! -f offscreen/index.ts ] && [ ! -f offscreen/index.html ] && [ ! -d offscreen ] && [ "$(grep -c 'copy-offscreen' vite.config.ts)" -eq 0 ] && npx tsc --noEmit</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `test ! -f offscreen/index.ts` exits 0 (file deleted)
|
|
- `test ! -f offscreen/index.html` exits 0 (file deleted)
|
|
- `test ! -d offscreen` exits 0 (directory removed)
|
|
- `grep -c "copy-offscreen" vite.config.ts` returns 0
|
|
- `grep -c "this.emitFile" vite.config.ts` returns 0 (T-1-NEW-06-01 grep gate)
|
|
- `grep -c "VideoRecorderDB" vite.config.ts` returns 0
|
|
- `grep -c "openIndexedDB" vite.config.ts` returns 0
|
|
- `grep -c "chromeMediaSource" vite.config.ts` returns 0
|
|
- `grep -c "rollupOptions" vite.config.ts` returns 1
|
|
- `grep -c "src/offscreen/index.html" vite.config.ts` returns 1
|
|
- `wc -l vite.config.ts` returns ≤ 30
|
|
- `npx tsc --noEmit` exits 0
|
|
- `npx vitest run` exits 0 (regression guard)
|
|
</acceptance_criteria>
|
|
<done>Inline plugin gone. Root-level offscreen/ gone. vite.config.ts is ~22 lines.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: BUILD VERIFY — run npm run build and reconcile the offscreen path</name>
|
|
<files>src/background/index.ts (only if path-adjustment needed)</files>
|
|
<read_first>
|
|
- vite.config.ts (post-Task-1 state)
|
|
- src/offscreen/index.html (from Plan 03)
|
|
- src/background/index.ts (line 85 area — the `chrome.runtime.getURL('offscreen/index.html')` call inherited from before Plan 05; verify with `grep -n "getURL" src/background/index.ts`)
|
|
- .planning/phases/01-stabilize-video-pipeline/01-RESEARCH.md §"Pitfall 5" (lines 838-856)
|
|
</read_first>
|
|
<action>
|
|
Run the build:
|
|
|
|
```bash
|
|
rm -rf dist
|
|
npm run build 2>&1 | tee /tmp/01-06-build.log
|
|
```
|
|
|
|
The build MUST exit 0. If it doesn't:
|
|
- Check the log for syntax/type errors and STOP — do not proceed to the rest of this task.
|
|
- Common failure modes: `chrome.offscreen.Reason.DISPLAY_MEDIA` not in current `@types/chrome` (fall back to the cast Plan 05 documented); the manifest reference to `desktopCapture` not yet landed (Plan 01 should have committed it — re-check); rollupOptions.input pointing at a non-existent file.
|
|
|
|
If the build succeeds, verify the dist layout:
|
|
|
|
```bash
|
|
ls -la dist/
|
|
ls -la dist/src/offscreen/ 2>/dev/null
|
|
ls -la dist/offscreen/ 2>/dev/null
|
|
```
|
|
|
|
There will be ONE of two outcomes:
|
|
|
|
**Outcome A — crxjs preserved `src/`**: `dist/src/offscreen/index.html` exists. In this case, the SW URL needs to be `chrome.runtime.getURL('src/offscreen/index.html')`.
|
|
|
|
**Outcome B — crxjs stripped `src/`**: `dist/offscreen/index.html` exists. In this case, the SW URL needs to be `chrome.runtime.getURL('offscreen/index.html')` (which is what the current Plan-05 file already has by accident — leftover from the pre-amendment code).
|
|
|
|
Identify which outcome obtains. Then edit `src/background/index.ts` line ~85 (inside `ensureOffscreen`):
|
|
|
|
```typescript
|
|
const url = chrome.runtime.getURL('<PATH>');
|
|
```
|
|
|
|
If Outcome A: `<PATH>` = `'src/offscreen/index.html'`.
|
|
|
|
If Outcome B: `<PATH>` = `'offscreen/index.html'` (leave unchanged).
|
|
|
|
Either way, after the edit (or no-edit), re-run:
|
|
|
|
```bash
|
|
npx tsc --noEmit
|
|
npm run build
|
|
ls -la dist/manifest.json
|
|
node -e "const m=require('./dist/manifest.json'); console.log('permissions:', m.permissions.join(','))"
|
|
```
|
|
|
|
The last line MUST print a comma-separated permission list that includes `desktopCapture` and does NOT include `tabCapture` (Plan 01 should already have ensured this in `manifest.json`; the `dist/manifest.json` is the crxjs-bundled output that propagates the manifest as-is).
|
|
|
|
Final layout verification:
|
|
|
|
```bash
|
|
find dist -type f -name "*.html" -o -name "*.js" | sort
|
|
```
|
|
|
|
The output should contain (in some order):
|
|
- `dist/manifest.json`
|
|
- `dist/src/popup/index.html` (or `dist/popup/index.html` — same Outcome-A/B rule)
|
|
- `dist/<offscreen path>/index.html` (whichever outcome)
|
|
- One or more `dist/assets/*.js` files (bundled SW, content script, offscreen TS, popup TS)
|
|
|
|
If the manifest.json's `background.service_worker` field doesn't resolve to an emitted file, STOP and audit. crxjs handles this automatically — there's only a problem if the manifest entry was hand-broken.
|
|
</action>
|
|
<verify>
|
|
<automated>npm run build && ls dist/manifest.json && [ -f dist/src/offscreen/index.html ] || [ -f dist/offscreen/index.html ]</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `npm run build` exits 0 (with NO TypeScript errors in /tmp/01-06-build.log)
|
|
- `dist/manifest.json` exists
|
|
- Either `dist/src/offscreen/index.html` OR `dist/offscreen/index.html` exists
|
|
- `dist/manifest.json` permissions list contains `"desktopCapture"` and does NOT contain `"tabCapture"`
|
|
- `src/background/index.ts` `chrome.runtime.getURL(...)` argument matches whichever Outcome (A or B) the build produced
|
|
- `dist/assets/` contains at least one `.js` file (the bundled SW / content / popup / offscreen scripts)
|
|
</acceptance_criteria>
|
|
<done>Build succeeds. SW URL string matches the dist layout. Plan 07 can load `dist/` into Chrome and the offscreen will resolve correctly.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
After both tasks land:
|
|
|
|
1. `npm run build` — exits 0, clean output.
|
|
2. `dist/` contains a loadable extension (manifest.json + at least one HTML offscreen page + at least one JS bundle in assets/).
|
|
3. `npx tsc --noEmit && npx vitest run` — both exit 0 (regression guard for the rest of the codebase).
|
|
4. `wc -l vite.config.ts` — ≤ 30 lines.
|
|
5. No top-level `offscreen/` directory.
|
|
6. The SW URL string in `src/background/index.ts` matches the actual `dist/` emit path (Outcome A or B).
|
|
|
|
Commit cadence: TWO commits.
|
|
- Task 1: ONE commit (`refactor(01-06): delete inline copy-offscreen plugin and orphan offscreen/ directory`).
|
|
- Task 2: ONE commit if path adjustment was needed (`fix(01-06): align ensureOffscreen URL with crxjs emit path`); ZERO commits if no path adjustment was needed (note in SUMMARY which Outcome obtained).
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- `vite.config.ts` is ~22 lines, contains crx() and rollupOptions only
|
|
- `offscreen/` top-level directory is GONE
|
|
- `dist/manifest.json` carries the post-Plan-01 amended permissions (desktopCapture, no tabCapture, no alarms)
|
|
- The SW URL string and the bundled HTML path match
|
|
- `npm run build` clean
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/01-stabilize-video-pipeline/01-06-SUMMARY.md` with:
|
|
- Pre / post line count for vite.config.ts (was 227, now ~22)
|
|
- Confirmation that `offscreen/` directory is gone (output of `find offscreen/ 2>&1 || echo absent`)
|
|
- The outcome (A or B) for crxjs's emit path, and the exact SW URL string committed
|
|
- Output of `find dist -type f | sort` after the post-Task-2 build, so Plan 07's manual smoke test knows what to load
|
|
- Two commit SHAs (or one if Task 2 needed no path adjustment)
|
|
- Note: "Plan 07 loads `dist/` unpacked into Chrome ≥ 116 and runs the ffprobe acceptance gate (D-12)."
|
|
</output>
|