7.6 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created
| phase | slug | status | nyquist_compliant | wave_0_complete | created |
|---|---|---|---|---|---|
| 1 | stabilize-video-pipeline | draft | false | false | 2026-05-15 |
Phase 1 — Validation Strategy
Per-phase validation contract for feedback sampling during execution. Derived from
01-RESEARCH.md§"Validation Architecture".
Test Infrastructure
| Property | Value |
|---|---|
| Framework | Vitest (Node mode) — recommended, NOT currently installed. Vite already in devDependencies, so Vitest is a zero-config-mismatch add. |
| Config file | NONE — Wave 0 creates vitest.config.ts. |
| Quick run command | npx vitest run --reporter=dot |
| Full suite command | npx vitest run && npx tsc --noEmit (+ grep guards) |
| Estimated runtime | ~10 s quick, ~30 s full |
Why not Jest: Vite is already the build tool; Vitest is the zero-config-mismatch choice. No transformer dance for TS.
Why not Playwright: MediaRecorder + getDisplayMedia ARE driveable in Chromium via Playwright with permissions auto-granted, but the acceptance gate (ffprobe on a real exported file) requires actually running the extension. Manual smoke + ffprobe is sufficient for Phase 1. Playwright-driven smoke tests are Phase 4/5 territory.
Sampling Rate
- After every task commit: Run
npx vitest run --reporter=dot && npx tsc --noEmit(≤ 10 s). - After every plan wave: Run
npx vitest run && npx tsc --noEmit && npm run build(≤ 30 s). - Before
/gsd-verify-work: Full suite + ffprobe gate (D-12) must be green. - Max feedback latency: 30 s.
Per-Task Verification Map
The exact task IDs are owned by PLAN.md. This map enumerates the behaviors and tests; the planner wires task IDs in once plans are written. Rows are sorted by acceptance-criterion order from CONTEXT.md.
| Behavior | Plan area | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|---|---|
Ring buffer adds chunk; first chunk gets isHeader: true (D-10) |
offscreen/recorder | 1 | REQ-video-ring-buffer | — | N/A | unit | npx vitest run tests/offscreen/ring-buffer.test.ts -t "first chunk is header" |
❌ Wave 0 | ⬜ pending |
| Ring buffer trims chunks older than 30 s; keeps header (D-11) | offscreen/recorder | 1 | REQ-video-ring-buffer | — | N/A | unit | npx vitest run tests/offscreen/ring-buffer.test.ts -t "trim 30s" |
❌ Wave 0 | ⬜ pending |
| Codec strict-mode throws when vp9 unsupported (D-20) | offscreen/recorder | 1 | REQ-video-ring-buffer | T-1-01 (codec downgrade) | Fail loud; no silent fallback to vp8/h264 | unit | npx vitest run tests/offscreen/codec-check.test.ts |
❌ Wave 0 | ⬜ pending |
| OFFSCREEN_READY handshake sent before SW emits START_RECORDING (Pattern 4) | offscreen + SW | 1 | REQ-video-ring-buffer | — | N/A | unit | npx vitest run tests/offscreen/handshake.test.ts |
❌ Wave 0 | ⬜ pending |
Port reconnect on onDisconnect within 1 s (Pattern 5) |
offscreen ↔ SW | 1 | REQ-video-ring-buffer | — | N/A | unit | npx vitest run tests/offscreen/port.test.ts -t "reconnects" |
❌ Wave 0 | ⬜ pending |
chrome.alarms keepalive deleted (D-18) |
SW | 1 | REQ-video-ring-buffer | — | N/A | grep | ! grep -RIn "chrome.alarms" src/background/ |
NO CODE NEEDED | ⬜ pending |
| IndexedDB SW path deleted (D-19) | SW | 1 | REQ-video-ring-buffer | — | N/A | grep | ! grep -RIn "VideoRecorderDB|openIndexedDB" src/ |
NO CODE NEEDED | ⬜ pending |
vite.config.ts:11-184 inline plugin deleted (D-08) |
build | 1 | REQ-video-ring-buffer | — | N/A | grep | ! grep -RIn "copy-offscreen|chromeMediaSource" vite.config.ts |
NO CODE NEEDED | ⬜ pending |
last_30sec.webm plays ffprobe-clean (D-12 acceptance gate) |
manual smoke | last | REQ-video-ring-buffer | — | N/A | integration | ffprobe -v error -f matroska -i sample/last_30sec.webm; test $? -eq 0 |
Sample produced during manual smoke; in CI a known-good fixture verifies the gate itself works | ⬜ pending |
Zero as any / @ts-ignore regressions in Phase 1 surface |
static | each | REQ-video-ring-buffer | — | N/A | static | npx tsc --noEmit && ! grep -RIn "as any|@ts-ignore" src/offscreen/ src/background/index.ts |
EXISTS (build script) | ⬜ pending |
| Manifest permission swap (D-A6 / D-05) | manifest | doc-cascade | REQ-video-ring-buffer | T-1-02 (excess permissions) | Drop tabCapture; add desktopCapture exactly |
grep | ! grep "tabCapture" manifest.json && grep "desktopCapture" manifest.json |
NO CODE NEEDED | ⬜ pending |
| Build produces a loadable extension | build | last | REQ-video-ring-buffer | — | N/A | manual | npm run build && ls dist/manifest.json dist/src/offscreen/index.html dist/assets/*.js |
NO TEST FILE; shell check | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Wave 0 Requirements
- Install Vitest:
npm install -D vitest @vitest/ui(verify current major vianpm view vitest versionat install time). vitest.config.ts— pull in path aliases fromtsconfig.json(if any); enable Node test env by default.tests/offscreen/directory with at minimum:tests/offscreen/ring-buffer.test.ts— D-10, D-11 (header pinning + 30 s trim).tests/offscreen/codec-check.test.ts— D-20 strict-mode error path.tests/offscreen/handshake.test.ts— Pattern 4 OFFSCREEN_READY.tests/offscreen/port.test.ts— Pattern 5 reconnect.
tests/fixtures/— keep a known-good WebM for ffprobe sanity (committed once; used by CI to verify the ffprobe gate runs at all).npm testscript inpackage.json:"test": "vitest run".chromeruntime stub for Vitest — light hand-rolled mock orvitest-chromedependency (decide at install time).
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
getDisplayMedia picker dialog appears and grants stream on operator click |
REQ-video-ring-buffer | Browser picker UX cannot be driven in Vitest; auto-grant via Playwright is Phase 4 territory | Load dist/ unpacked; click extension; observe picker; pick "Entire screen"; confirm "Sharing" indicator appears; SW console shows [Offscreen] Stream created. |
last_30sec.webm actually plays in a browser (SPEC §10 #7) |
REQ-video-ring-buffer | Decoder behavior depends on real Chromium media stack | Open the exported last_30sec.webm in Chrome; confirm video plays from start to end; no playback freezes. |
| SW idle survival (D-16, D-17) | REQ-video-ring-buffer | Requires real MV3 SW lifecycle | DevTools → Extensions → Service Worker → "Force stop"; wait 60 s; click extension save; confirm exported archive contains video chunks from before "Force stop". |
| "Stop sharing" recovery (D-03) | REQ-video-ring-buffer | Requires real browser stream lifecycle | Click "Stop sharing" in Chrome's screen-share banner; click extension save; expect graceful "no recording — re-prompt" UX (Phase 3 owns the popup state; Phase 1 owns the offscreen-side error signal). |
Validation Sign-Off
- All tasks have
<automated>verify or Wave 0 dependencies (planner enforces) - Sampling continuity: no 3 consecutive tasks without automated verify
- Wave 0 covers all MISSING references (Vitest install + 4 test files + fixtures)
- No watch-mode flags (Vitest
runmode only) - Feedback latency < 30 s per wave
nyquist_compliant: trueset in frontmatter (planner flips this once PLAN.md tasks reference these test commands)
Approval: pending