15 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, requirements_addressed, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | requirements_addressed | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-stabilize-video-pipeline | 06 | execute | 2 |
|
|
true |
|
|
|
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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 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:
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.
<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: \` })` |
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> |
(1) Delete offscreen/index.ts (the orphan dead-code file):
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):
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):
[ -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:
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:
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).
[ ! -f offscreen/index.ts ] && [ ! -f offscreen/index.html ] && [ ! -d offscreen ] && [ "$(grep -c 'copy-offscreen' vite.config.ts)" -eq 0 ] && npx tsc --noEmit
<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>
Inline plugin gone. Root-level offscreen/ gone. vite.config.ts is ~22 lines.
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_MEDIAnot in current@types/chrome(fall back to the cast Plan 05 documented); the manifest reference todesktopCapturenot 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:
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):
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:
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:
find dist -type f -name "*.html" -o -name "*.js" | sort
The output should contain (in some order):
dist/manifest.jsondist/src/popup/index.html(ordist/popup/index.html— same Outcome-A/B rule)dist/<offscreen path>/index.html(whichever outcome)- One or more
dist/assets/*.jsfiles (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.
npm run build && ls dist/manifest.json && [ -f dist/src/offscreen/index.html ] || [ -f dist/offscreen/index.html ]
<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>
Build succeeds. SW URL string matches the dist layout. Plan 07 can load dist/ into Chrome and the offscreen will resolve correctly.
npm run build— exits 0, clean output.dist/contains a loadable extension (manifest.json + at least one HTML offscreen page + at least one JS bundle in assets/).npx tsc --noEmit && npx vitest run— both exit 0 (regression guard for the rest of the codebase).wc -l vite.config.ts— ≤ 30 lines.- No top-level
offscreen/directory. - The SW URL string in
src/background/index.tsmatches the actualdist/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).
<success_criteria>
vite.config.tsis ~22 lines, contains crx() and rollupOptions onlyoffscreen/top-level directory is GONEdist/manifest.jsoncarries the post-Plan-01 amended permissions (desktopCapture, no tabCapture, no alarms)- The SW URL string and the bundled HTML path match
npm run buildclean </success_criteria>