Pure type-extension step for the Option C architectural refactor of the
offscreen↔SW port lifecycle (.planning/debug/empty-archive-port-race.md).
PortMessage.requestId is optional so PING/PONG keep their no-payload
shape. REQUEST_BUFFER and BUFFER will populate it once the recorder
(offscreen) and getVideoBufferFromOffscreen (SW) are wired up in the
next commits. The narrowing remains structural — every consumer must
still validate `type` before accessing `requestId`.
PONG joins PING as a no-payload liveness signal. The SW echoes PONG on
PING, closing the health-probe loop the offscreen uses to detect a dead
port (replacing the 290 s pre-emptive setTimeout that was the proximate
cause of the H1 race window per the bisect).
No runtime change. 52 tests, 10 RED still RED (waiting for impl).
tsc --noEmit exit 0; type-safety grep clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure rename pass — zero behavioural change at any call site. The
prior "chunk" naming is a vestige of D-09..D-11's chunk-level
buffer; under D-13 the unit of transfer is a self-contained ~10 s
WebM segment, so the type name now matches the data shape.
Renames propagated atomically:
- src/shared/types.ts
* TransferredVideoChunk → TransferredVideoSegment
(also: the `isFirst?: boolean` field is dropped — D-13 segments
are all implicitly their own header, so the flag is meaningless
and only existed for the retired ring-buffer's pin semantics)
* VideoChunk → VideoSegment (drops `isFirst?` for the same reason)
* PortMessage.chunks? → PortMessage.segments?
* VideoBufferResponse.chunks → VideoBufferResponse.segments
- src/offscreen/recorder.ts
* import type rename
* encodeAndSendBuffer's per-element accumulator + filter type
* the port.postMessage payload field
- src/background/index.ts
* import type rename
* getVideoBufferFromOffscreen reads `(msg as {segments?}).segments`
(matching the new wire field name)
* empty-buffer returns `{ segments: [] }`
* mergeVideoSegments signature takes VideoSegment[]
* createArchive consumes videoBufferResponse.segments
* saveArchive log line says "segments"
Verification:
- npx tsc --noEmit clean.
- npx vitest run → 28 passed / 2 failed (the 2 fixture-empirical
ffmpeg dry-runs; unchanged — they're gated on ./smoke.sh regen).
- npm run build succeeds, all 60 modules transformed.
- grep predicates clean in src/:
* no addChunk / trimAged / firstChunkSaved / isFirst
* no TransferredVideoChunk / VideoChunk (old names)
* 21 occurrences of new names propagated correctly
- Pre-existing port-serialization.test.ts still GREEN: its `isFirst`
references are inside inline-test-scoped objects (not imports
from production types), and its tests assert JSON-roundtrip
behaviour rather than the production type shape.
- Add TransferredVideoChunk { data: string (base64); type: string
(MIME); timestamp: number; isFirst?: boolean } as the
JSON-serializable shape for chunks traversing the offscreen↔SW
chrome.runtime.Port boundary.
- Retarget PortMessage.chunks?: TransferredVideoChunk[] (was
VideoChunk[]). The in-memory VideoChunk type is unchanged — both
the offscreen ring buffer and the SW-side merge step keep using
it after decoding the wire payload.
- No behavior change yet; this commit only lands the type. The
encode/decode call sites land in the next two commits.
Refs: debug session d12-blob-port-transfer-fails.
- Add blobToBase64 / base64ToBlob in src/shared/binary.ts:
portable Blob↔base64 round-trip for the chrome.runtime.Port
wire-format. JSON.stringify(blob) returns "{}" across extension
contexts, so binary payloads must travel as base64 strings.
- Mirror the GREEN-block helper signatures from
tests/offscreen/port-serialization.test.ts so the same test pins
both the standalone helpers and the production wire format.
- Land tests/offscreen/port-serialization.test.ts as the RED+GREEN
executable contract for the D-12 fix: the RED block reproduces
the 75-byte "[object Object]" failure mode byte-for-byte; the
GREEN block pins the base64 wire-format the fix must implement.
- Uses arrayBuffer() + btoa(String.fromCharCode...) rather than
FileReader: FileReader is browser-only; the chosen approach
works in both Chrome extension contexts and the Node-based
vitest environment.
Refs: debug session d12-blob-port-transfer-fails.
- Add PortMessageType and PortMessage interface to src/shared/types.ts
for the long-lived port (offscreen ↔ SW; D-17 / Plan 04 wires the
ping loop + REQUEST_BUFFER / BUFFER traffic).
- Remove 'VIDEO_CHUNK' and 'VIDEO_CHUNK_SAVED' from MessageType union
(per D-19 — chunks no longer travel via chrome.runtime.sendMessage;
the IndexedDB SW-side plumbing is the audit P0 #2 broken path).
- OffscreenLogger class was already added alongside Task 2 because
recorder.ts imports it at module top.
Inline SW cleanup (Rule 3 — blocking dependency, plan acceptance gates
on `npx tsc --noEmit` exit 0):
- Remove src/background/index.ts VIDEO_CHUNK + VIDEO_CHUNK_SAVED case
branches (refs to deleted union members).
- Remove now-unreferenced loadChunkFromIndexedDB / openIndexedDB
(only reachable from the deleted VIDEO_CHUNK_SAVED branch).
- Remove now-unreferenced addVideoChunkFromBlob / cleanupVideoBuffer
/ firstChunkSaved / VIDEO_BUFFER_DURATION_MS constant (the SW-side
ring buffer now lives in src/offscreen/recorder.ts per D-16).
- Keep SW-side `videoBuffer: VideoChunk[] = []` as a placeholder; Plan
04 wires it to fetch from offscreen over the keepalive port. The
remaining `getVideoBuffer` + `saveArchive` callers continue to
compile against the empty array until Plan 04 lands.
- Plan 05 owns the broader SW shell cleanup.
Verification (post-commit):
- npx vitest run tests/offscreen/ring-buffer.test.ts tests/offscreen/codec-check.test.ts → 6/6 PASS
- npx tsc --noEmit → exit 0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add src/offscreen/recorder.ts (214 lines) — Phase 01 source of truth
owning getDisplayMedia capture, MediaRecorder lifecycle, in-memory ring
buffer with WebM header retention + 30 s age trim, codec strict-mode
(D-20), and track.onended cleanup.
- Add src/offscreen/index.html — crxjs-managed bundle entry referencing
./recorder.ts.
- Add OffscreenLogger class to src/shared/logger.ts (uses ...args:
unknown[] for strict-mode hygiene; legacy Logger / ContentLogger keep
...args: any[] per project provenance). Bundled into this commit
because recorder.ts cannot typecheck without the import (Rule 3 —
blocking dependency).
- Pre-stage D-13 restart-segments fallback as commented skeleton at
bottom of recorder.ts so Plan 07's fallback path needs no re-plan.
- Defensive bootstrap (typeof chrome guard) so the pure ring-buffer +
codec tests can import the module without stubbing the full chrome
surface (Rule 3 — Plan 02 ring-buffer test does not stub chrome).
Flips Plan 02's RED tests to GREEN:
- tests/offscreen/ring-buffer.test.ts — 4 tests passing
- tests/offscreen/codec-check.test.ts — 2 tests passing
Handshake test also passes (single OFFSCREEN_READY emission); port
reconnect test stays RED until Plan 04 wires the reconnect loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>