Files
mokosh/.planning/phases/01-stabilize-video-pipeline/01-03-SUMMARY.md
Mark 30e5efd364 docs(01-03): complete offscreen recorder TDD plan
- Add 01-03-SUMMARY.md documenting RED -> GREEN gate (Plan 02 tests now
  pass), 3 Rule-3 auto-fixes (OffscreenLogger inline, defensive
  bootstrap, SW dead-code removal), and Plan 04 / 05 handoff notes.
- Update STATE.md: advance plan counter to 4 of 7 (43%), append
  metrics + 3 execution decisions, record session.
- Update ROADMAP.md: mark Plan 01-03 [x] complete.

REQ-video-ring-buffer remains NOT complete — still pending Plans 04
(port keepalive) and 07 (ffprobe acceptance gate).

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

16 KiB
Raw Blame History

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 03 offscreen-recorder
mediarecorder
getDisplayMedia
vp9
ring-buffer
tdd
chrome-extension
mv3
phase provides
01-stabilize-video-pipeline Plan 02 RED tests pinning ring-buffer + codec contracts
src/offscreen/recorder.ts — canonical offscreen recorder module (ring buffer + getDisplayMedia + codec strict-mode + track-ended cleanup)
src/offscreen/index.html — crxjs-managed bundle entry
OffscreenLogger class in src/shared/logger.ts (uses unknown[] for strict-mode hygiene)
PortMessage / PortMessageType types in src/shared/types.ts
Removed broken VIDEO_CHUNK / VIDEO_CHUNK_SAVED message variants (audit P0 #2 dead path)
D-13 restart-segments fallback skeleton pre-staged (no re-plan needed if Plan 07 ffprobe fails)
01-04-port-keepalive
01-05-sw-shrink
01-06-vite-config
01-07-ffprobe-gate
added patterns
Pure ring-buffer functions exported for Node-testable unit tests
Defensive bootstrap with typeof chrome guard (allows pure-function tests to import the module)
Codec strict-mode (assertCodecSupported throws + emits RECORDING_ERROR before throw)
OffscreenLogger uses ...args: unknown[] (strict-mode hygiene divergence from legacy Logger/ContentLogger which keep any[])
created modified
src/offscreen/recorder.ts
src/offscreen/index.html
src/shared/logger.ts
src/shared/types.ts
src/background/index.ts
Bootstrap section guarded with typeof chrome check (Rule 3) so the pure ring-buffer test can import the module without a chrome stub
OffscreenLogger added in Task 2 commit (recorder.ts cannot typecheck without the import — Rule 3 blocking dependency)
VIDEO_CHUNK / VIDEO_CHUNK_SAVED SW-side handlers and IndexedDB plumbing deleted inline (Rule 3) — removing types without removing referencing branches would break tsc-clean acceptance
Task 4 refactor pass: no obvious improvements — SKIPPED
Pure functions (addChunk / trimAged / getBuffer / resetBuffer / assertCodecSupported) exported separately from impure side-effects (getDisplayMedia / MediaRecorder lifecycle) — keeps Node-only test discipline
Defensive chrome.runtime guards in bootstrap so the module is import-safe under partial test stubs (the handshake + port tests stub the full chrome surface; the ring-buffer test stubs nothing)
Russian section header comments preserved per project provenance (CONTEXT.md 'Established patterns')
8min 2026-05-15

Phase 01 Plan 03: Offscreen Recorder TDD GREEN Summary

vp9-strict offscreen recorder module with header-pinned 30 s ring buffer, getDisplayMedia capture, MediaRecorder lifecycle, and track-ended cleanup — flipped Plan 02 ring-buffer + codec-check tests from RED to GREEN.

Performance

  • Duration: 8 min
  • Started: 2026-05-15T15:30:29Z
  • Completed: 2026-05-15T15:38:42Z
  • Tasks: 4 (3 executed, 1 SKIPPED per Task 4 rules)
  • Files modified: 5 (2 created, 3 modified)

Accomplishments

  • GREEN gate cleared: tests/offscreen/ring-buffer.test.ts (4 tests) and tests/offscreen/codec-check.test.ts (2 tests) — all 6 now pass.
  • Canonical recorder module: src/offscreen/recorder.ts (214 lines) owns the ring buffer + getDisplayMedia + MediaRecorder lifecycle + codec strict-mode (D-20) + track.onended cleanup (D-03).
  • Codec strict-mode enforced: assertCodecSupported() calls MediaRecorder.isTypeSupported('video/webm;codecs=vp9') and on failure emits RECORDING_ERROR to SW before throwing — T-1-01 mitigation, no fallback chain.
  • Header retention + 30 s age trim: ring-buffer pure functions implement D-10 (first chunk pinned indefinitely) and D-11 (drop later chunks older than 30 s by arrival timestamp).
  • D-13 fallback skeleton pre-staged as a commented restart-segments block at the bottom of recorder.ts so Plan 07's potential fallback path does not require a re-plan.
  • Strict-type hygiene: zero as any, zero @ts-ignore, zero fallback codec strings in src/offscreen/.

TDD Gate Sequence

Gate Command Result
RED (pre-implementation) npx vitest run tests/offscreen/ring-buffer.test.ts tests/offscreen/codec-check.test.ts exit 1, Cannot find module '../../src/offscreen/recorder' (captured to /tmp/01-03-red.log)
GREEN (post-implementation) npx vitest run tests/offscreen/ring-buffer.test.ts tests/offscreen/codec-check.test.ts exit 0, 6 tests pass (captured to /tmp/01-03-green.log)
REFACTOR inspection of src/offscreen/recorder.ts no obvious improvements — SKIPPED per Task 4 rules

RED log excerpt

FAIL  tests/offscreen/ring-buffer.test.ts [ tests/offscreen/ring-buffer.test.ts ]
Error: Cannot find module '../../src/offscreen/recorder' imported from
  /home/parf/projects/work/repremium/tests/offscreen/ring-buffer.test.ts

GREEN test output (6/6 PASS)

RUN  v4.1.6 /home/parf/projects/work/repremium
······
Test Files  2 passed (2)
     Tests  6 passed (6)

Test names now passing:

  1. ring buffer > first chunk is header
  2. ring buffer > second chunk is NOT header
  3. ring buffer > trim 30s — keeps header, evicts aged tail
  4. ring buffer > trim with empty buffer does not throw
  5. codec strict mode > throws on unsupported vp9 and emits RECORDING_ERROR
  6. codec strict mode > does not throw when vp9 IS supported

Task Commits

# Task Type Commit
1 RED-verify (no commit per plan — verify-only)
2 GREEN — write recorder.ts + index.html (bundled OffscreenLogger inline due to Rule-3 dependency) feat fff1aea
3 OffscreenLogger + types cleanup (bundled SW dead-code removal due to Rule-3 dependency) feat c5828d3
4 REFACTOR — SKIPPED (no obvious improvements)

Final metadata commit will follow after STATE.md / ROADMAP.md updates.

Export Surface of src/offscreen/recorder.ts

For Plans 04 / 05 to grep against without re-reading:

// constants
export const VIDEO_BUFFER_DURATION_MS: number;     // 30_000

// pure ring-buffer functions
export function addChunk(blob: Blob, timestamp: number): void;
export function trimAged(now: number): void;
export function getBuffer(): VideoChunk[];
export function resetBuffer(): void;

// codec strict-mode (throws on unsupported vp9; emits RECORDING_ERROR before throw)
export function assertCodecSupported(): void;

Import-time side effects (from bootstrap() — guarded by typeof chrome !== 'undefined'):

  • chrome.runtime.onMessage.addListener(...) — registered exactly once
  • chrome.runtime.connect({ name: 'video-keepalive' }) — called exactly once
  • chrome.runtime.sendMessage({ type: 'OFFSCREEN_READY' }) — called exactly once

Files Created / Modified

File Status Lines Purpose
src/offscreen/recorder.ts created 214 Canonical offscreen recorder (D-01..D-13, D-20)
src/offscreen/index.html created 9 crxjs bundle entry referencing ./recorder.ts
src/shared/logger.ts modified +28 Added OffscreenLogger class (uses unknown[] per plan style note)
src/shared/types.ts modified -2 / +9 Removed VIDEO_CHUNK/VIDEO_CHUNK_SAVED from MessageType; added PortMessageType + PortMessage
src/background/index.ts modified -91 Removed dead VIDEO_CHUNK + VIDEO_CHUNK_SAVED case branches and unreachable helper functions (Rule 3 — see Deviations)

Threat Mitigations Verified

  • T-1-01 (codec downgrade tampering): assertCodecSupported() calls MediaRecorder.isTypeSupported('video/webm;codecs=vp9') strict; on failure emits RECORDING_ERROR to SW BEFORE throwing. Grep gate: grep -v '^#' src/offscreen/recorder.ts | grep -cE 'codecs=(vp8|h264)' returns 0. Unit test mocking isTypeSupported -> false confirms throw + sendMessage call.
  • T-1-03 (stream content leakage): Accepted residual risk per CONTEXT.md D-04. Code-level mitigation deferred: the getDisplayMedia call captures whatever the user picks; logging track.label for support visibility is Plan 04 work (the bootstrap defers to Plan 04 for the port handler that surfaces this).
  • T-1-NEW-03-01 (unbounded buffer): trimAged is a pure filter over arrival timestamps; defensive grep + tests confirm trim is idempotent on empty buffer and never grows past header + chunks newer than now - 30 000 ms.

Decisions Made

  1. OffscreenLogger style divergence: uses ...args: unknown[] per plan style_divergence_note; legacy Logger / ContentLogger stay on any[]. Plan 03 does NOT refactor the legacy classes — they remain per project provenance.
  2. Bootstrap defensive guard: the bootstrap() function checks typeof chrome !== 'undefined' before registering any side effects. This was the minimal change needed for the pure ring-buffer test (which stubs nothing) to import the module without crashing. Plan 04 will replace this stub with the full reconnect + ping loop.
  3. Task 4 refactor SKIPPED: inspection found no obvious improvements (no constant duplication, no unused imports, no mis-placed comments). Per Task 4 rules, do not commit if no changes.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] OffscreenLogger bundled into Task 2 commit instead of Task 3

  • Found during: Task 2 GREEN implementation
  • Issue: src/offscreen/recorder.ts imports OffscreenLogger from ../shared/logger (per plan verbatim code), but Task 3 was scheduled to ADD that class. The Task 2 acceptance gate npx tsc --noEmit cannot pass without the import resolving.
  • Fix: Added the OffscreenLogger class to src/shared/logger.ts as part of the Task 2 commit. Task 3 commit then handled only the src/shared/types.ts cleanup + SW dead-code removal.
  • Files modified: src/shared/logger.ts (in Task 2 commit fff1aea)
  • Verification: grep -c "export class OffscreenLogger" src/shared/logger.ts returns 1; npx tsc --noEmit exits 0.

2. [Rule 3 - Blocking] Defensive bootstrap guard added — pure ring-buffer test does not stub chrome

  • Found during: Task 2 — first GREEN run after writing the verbatim plan code
  • Issue: The plan's verbatim bootstrap code unconditionally accesses chrome.runtime.onMessage.addListener, chrome.runtime.connect, and chrome.runtime.sendMessage. The Plan 02 ring-buffer test imports the module but does NOT stub chrome (intentionally — it tests pure functions). Result: ReferenceError: chrome is not defined on every import. The codec-check test stubs chrome.runtime.sendMessage only, so it also crashed on the missing onMessage.addListener.
  • Fix: Wrapped the bootstrap in a function with typeof chrome === 'undefined' / per-API-existence guards. The handshake + port tests provide the full chrome stub, so they still observe the side effects; the pure-function tests no-op the bootstrap. Plan 04 will replace this stub with the full reconnect + ping loop on top of the guarded structure.
  • Files modified: src/offscreen/recorder.ts (in Task 2 commit fff1aea)
  • Verification: npx vitest run tests/offscreen/ring-buffer.test.ts tests/offscreen/codec-check.test.ts → 6/6 PASS; the handshake test still passes (single OFFSCREEN_READY emission observed).

3. [Rule 3 - Blocking] Removed SW-side VIDEO_CHUNK + VIDEO_CHUNK_SAVED branches and unreachable IndexedDB helpers

  • Found during: Task 3 — first tsc run after removing the union members
  • Issue: Removing VIDEO_CHUNK / VIDEO_CHUNK_SAVED from MessageType (Task 3 acceptance criterion) broke two case branches in src/background/index.ts:457-473. Leaving them would have caused TS2678 errors; tsc-clean is a Task 3 acceptance gate. The branches referenced addVideoChunkFromBlob and loadChunkFromIndexedDB. After deleting the case branches, loadChunkFromIndexedDB had no callers (TS6133 unused). Cascading: openIndexedDB, addVideoChunkFromBlob, cleanupVideoBuffer, firstChunkSaved, the SW-side VIDEO_BUFFER_DURATION_MS constant all became unused.
  • Fix: Deleted the case branches and the now-unreachable helper functions and state variables. Kept videoBuffer: VideoChunk[] = [] (still referenced by getVideoBuffer and saveArchive) as an empty placeholder; Plan 04 wires it to fetch from offscreen over the keepalive port. CONTEXT.md "Files to DELETE in this phase" explicitly lists these functions as Phase 1 delete targets; the attribution between plans 03 and 05 was not strictly enumerated. Plan 05 still owns the broader SW shrink.
  • Files modified: src/background/index.ts (in Task 3 commit c5828d3) — net -91 lines (115 removed, 24 added)
  • Verification: npx tsc --noEmit exits 0; ring-buffer + codec tests still pass; SW module compiles cleanly.

Total deviations: 3 auto-fixed (3× Rule 3 blocking dependencies) Impact on plan: All three were inevitable consequences of the plan's TDD ordering — Tasks 2 and 3 had cross-dependencies the plan author glossed over. None introduced new architecture; all stayed within Phase 1's CONTEXT.md authorization. No scope creep beyond what CONTEXT.md "Files to DELETE in this phase" already sanctioned.

Issues Encountered

  • Plan 04 port-reconnect test stays RED (per plan <verification> line 646 — this is intentional). The handshake test passes (single OFFSCREEN_READY emission), so wave-2 of the TDD choreography is partially complete; Plan 04 will flip the remaining reconnect test to GREEN.

Threat Flags

None — no new security-relevant surface was introduced beyond what the plan's <threat_model> already enumerated (T-1-01, T-1-03, T-1-NEW-03-01).

Plan 04 / 05 Handoff

Plan 04 needs to:

  1. Replace the import-time stub chrome.runtime.connect({ name: 'video-keepalive' }) with the full ping-loop + reconnect-on-disconnect from RESEARCH.md Pattern 5.
  2. Wire the REQUEST_BUFFER handler so the SW can pull chunks on export (uses the PortMessage types added in this plan).
  3. Confirm the existing OFFSCREEN_READY send is still emitted exactly once (the handshake test should remain green after Plan 04's refactor pass).
  4. Once Plan 04 lands, the void keepalivePort; line near the bottom of recorder.ts becomes dead and should be removed in Plan 04's refactor.

Plan 05 needs to:

  • Update SW side: replace the empty videoBuffer placeholder with a port-driven fetch; collapse the remaining SW shell (getVideoBuffer, saveArchive integration with offscreen).
  • Delete setupKeepalive + alarms code (audit P1 #8 / D-18) — this plan left it intact.

Next Phase Readiness

REQ-video-ring-buffer is NOT yet complete — it remains tied to Plans 04 (port keepalive) and 07 (ffprobe gate). Do NOT mark the requirement complete; STATE.md will reflect Plan 03 done but the requirement still pending.

Self-Check: PASSED

  • [x] src/offscreen/recorder.ts exists (214 lines, exit 0 from test -f)
  • [x] src/offscreen/index.html exists (9 lines)
  • [x] src/shared/logger.ts updated (OffscreenLogger class found via grep)
  • [x] src/shared/types.ts updated (VIDEO_CHUNK removed, PortMessage added)
  • [x] src/background/index.ts updated (dead code removed)
  • [x] Commit fff1aea exists (git log --oneline | grep fff1aea returns 1)
  • [x] Commit c5828d3 exists (git log --oneline | grep c5828d3 returns 1)
  • [x] GREEN tests pass (verified by /tmp/01-03-green.log and live re-run)
  • [x] tsc --noEmit exits 0
  • [x] T-1-01 grep gate returns 0 (no fallback codec strings)
  • [x] D-13 marker present (// FALLBACK D-13: restart-segments grep returns 1)

Phase: 01-stabilize-video-pipeline Completed: 2026-05-15