# Phase 1: Stabilize Video Pipeline — Discussion Log

> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

**Date:** 2026-05-15
**Phase:** 01-stabilize-video-pipeline
**Areas discussed:** Offscreen source-of-truth location, Ring-buffer mechanism, Tab-switch behavior, State survival across SW unload

---

## Offscreen source-of-truth location

| Option | Description | Selected |
|--------|-------------|----------|
| `src/offscreen/recorder.ts` as crxjs offscreen entry | Real TS module bundled by crxjs; HTML references the bundle; type-check + source maps; deletes both `offscreen/index.ts` and the inline `copy-offscreen` plugin | ✓ |
| `src/offscreen/recorder.ts` via `web_accessible_resources` | Same source file, declared as WAR; reachable from `chrome.runtime.getURL()`; more boilerplate | |
| Keep vite.config.ts inline string | Status quo — hard NO per audit; no type-check, no source maps, easy to lose | |

**User's choice:** crxjs offscreen entry (Recommended)
**Notes:** Aligns with the audit's primary structural recommendation. The inline plugin and dead `offscreen/index.ts` are both deleted in the same phase.

---

## Ring-buffer mechanism

| Option | Description | Selected |
|--------|-------------|----------|
| Single continuous MediaRecorder + age-based trim (`start(2000)`) | Spec-aligned timeslice; retain header forever, drop later chunks by age; verify with ffprobe; pivot to restart-segments if verification fails | ✓ |
| Restart-segments every ~10 s | Each segment self-contained valid WebM; bulletproof playability; ~200–300 ms gap per restart (~2–3% loss); higher RAM (3 segments in flight) | (fallback) |
| Cluster-aware EBML trim via ts-ebml | Theoretically optimal; ~80 KB dep + more test surface | (third fallback, in deferred) |
| Chunk-arrival-time trim, 200 ms timeslice (status quo) | Current broken approach | |

**User's choice:** "It seems that 2 is the most stable? But if you think 1 is better it is up to you."
After follow-up: locked **Option 1** with **Option 2 as documented fallback**.
**Notes:** Decision deferred to Claude after weighing the trade-offs; rationale captured in CONTEXT.md D-09..D-13. Plan-checker enforces ffprobe verification as the Phase 1 success gate; if it fails, the plan revision pivots to restart-segments without re-asking the user.

---

## Tab-switch behavior

| Option | Description | Selected |
|--------|-------------|----------|
| Hard restart (stop old recorder, start fresh on new tab) | SPEC §8-implied; spec-aligned | (rendered N/A by capture-API change) |
| Cross-tab carry-over | Preserve prior tab's buffer until new fills; ~2× RAM during overlap | |
| Per-tab buffers (one per recently-visited tab) | High RAM; risks CON-ram-ceiling | |
| **(emergent option from user: capture whole browser / whole screen)** | Switch capture API to `getDisplayMedia()` / `chrome.desktopCapture`; tab-switch handling becomes N/A | ✓ |

**User's choice:** "May we just record the whole browser? or the whole screen?" → confirmed in the follow-up question as **switch to chrome.desktopCapture / getDisplayMedia**.
**Notes:** This is a project-level amendment to DEC-003 (capture API), not just a Phase 1 implementation detail. Trade-off was made explicit to the user before confirming: Chrome shows a permanent "Sharing your screen" banner (loses SPEC §1 "silent for operator" property), picker dialog required on session start. User accepted. Phase 1's plan therefore includes the doc-amendment cascade (D-A1..D-A6) BEFORE any code work, and removes all `chrome.tabs.onActivated`/`onUpdated` reattachment logic.

---

## State survival across SW unload

| Option | Description | Selected |
|--------|-------------|----------|
| Move buffer ownership to offscreen + long-lived port keepalive | Offscreen survives SW; port resets idle timer (only mechanism that does); buffer in plain offscreen memory; honors CON-buffer-storage | ✓ |
| Same as above + chrome.storage.session belt-and-suspenders | Cold-start recovery on offscreen crash; borderline CON-buffer-storage compliance | (deferred to Phase 5) |
| Keep SW-owned buffer, add proper keepalive port | Smallest diff; SW can still die under memory pressure | |
| Accept buffer loss on SW unload | Don't fight lifecycle; violates "continuous" intent | |

**User's choice:** "one seems no problem." — locked **Option 1**.
**Notes:** `chrome.alarms` keepalive deleted (DEC-010 / CON-service-worker-keepalive amended). The `chrome.storage.session` belt-and-suspenders variant moved to Phase 5 deferred ideas. The SW-side IndexedDB code path in `vite.config.ts:43-104` and `src/background/index.ts:445-475` is deleted alongside.

---

## Claude's Discretion

- Internal protocol choice between `chrome.runtime.connect` port and `chrome.runtime.sendMessage` for offscreen↔SW messaging (decision: port for keepalive, sendMessage for one-shot requests — captured in CONTEXT.md D-19).
- Codec strictness — enforce vp9 only via `MediaRecorder.isTypeSupported`, fail loud if unavailable. No fallback chain (current code's vp9→vp8→h264→default is removed).
- Internal module naming for the offscreen recorder.
- Exact error-UX copy for "user stopped sharing" — deferred to Phase 3 (popup state machine territory).
- Code-style choices within `src/offscreen/` (already constrained by `tsconfig` strict mode).

## Deferred Ideas

- **Audio capture.** `getDisplayMedia({ audio: true })` makes this trivial — but SPEC §9 excludes audio from Phase 1. Phase 2 work.
- **Per-tab silent capture mode** (re-introducing `tabCapture` as an opt-in via `config.json`) for installations that prioritize silent operation over broad coverage.
- **Cluster-aware EBML trim (ts-ebml)** — third-line fallback under D-13 if even restart-segments don't produce playable output. Currently parked.
- **`chrome.storage.session` cold-start buffer-pointer recovery** — Phase 5 (Harden + clean up).
- **Error UX for "user stops sharing"** — popup state extension, Phase 3.
