---
phase: 01-stabilize-video-pipeline
plan: 01
type: execute
wave: 0
depends_on: []
files_modified:
- .planning/PROJECT.md
- .planning/REQUIREMENTS.md
- .planning/ROADMAP.md
- .planning/intel/decisions.md
- .planning/intel/constraints.md
- manifest.json
autonomous: true
scope_exception: "doc-text-only edits with no TypeScript compilation; cognitive load substantially lower than code plans of equal task count"
requirements:
- REQ-video-ring-buffer
requirements_addressed:
- REQ-video-ring-buffer
must_haves:
truths:
- "PROJECT.md DEC-003, DEC-010 reflect the getDisplayMedia + port-keepalive amendments"
- "REQUIREMENTS.md REQ-video-ring-buffer no longer says 'active-tab' and binds to getDisplayMedia"
- "ROADMAP.md Phase 1 Success Criterion #2 no longer references tab re-attach"
- "intel/decisions.md DEC-003 and DEC-010 carry an Amendment block"
- "intel/constraints.md CON-tab-capture-binding and CON-service-worker-keepalive are RETIRED with a CON-display-capture-binding added"
- "manifest.json permissions list contains desktopCapture (not tabCapture) and drops the now-unused alarms entry"
- "Every code-touching plan (02-07) sees a consistent doc baseline before it runs"
artifacts:
- path: ".planning/PROJECT.md"
provides: "Amended DEC-003 / DEC-010 rows and amended Constraints section"
contains: "getDisplayMedia"
- path: ".planning/REQUIREMENTS.md"
provides: "REQ-video-ring-buffer wording without 'active-tab' and bound to getDisplayMedia"
contains: "getDisplayMedia"
- path: ".planning/ROADMAP.md"
provides: "Phase 1 Success Criterion #2 with tab-reattach clause removed"
contains: "MediaRecorder"
- path: ".planning/intel/decisions.md"
provides: "DEC-003 and DEC-010 with Amendment blocks"
contains: "## Amendment"
- path: ".planning/intel/constraints.md"
provides: "RETIRED markers + new CON-display-capture-binding"
contains: "CON-display-capture-binding"
- path: "manifest.json"
provides: "Permission swap"
contains: "desktopCapture"
key_links:
- from: "manifest.json"
to: "intel/constraints.md"
via: "lockstep permission set"
pattern: "desktopCapture"
- from: "intel/decisions.md"
to: "PROJECT.md"
via: "Amendment block referenced from Key Decisions table"
pattern: "AMENDED"
---
Doc-cascade for D-A1..D-A6 + manifest permission swap (D-05/D-A6). This is
Wave 0 work that MUST precede every code-touching plan in this phase so
downstream agents see a consistent baseline (DEC-003 amended to
`getDisplayMedia`, DEC-010 amended to port keepalive, manifest permissions
swapped to `desktopCapture`). Plan 01 makes zero code-runtime changes — only
text edits to planning docs and a single permissions edit in `manifest.json`.
Purpose: Phase 2 and later phases will read PROJECT.md / REQUIREMENTS.md /
ROADMAP.md to understand what was decided. Leaving them stale would carry the
old `chrome.tabCapture` contract forward and cause downstream agents to
silently re-introduce active-tab assumptions.
Output: Six amended doc files + the manifest permissions block in its final
Phase-1 shape.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/REQUIREMENTS.md
@.planning/ROADMAP.md
@.planning/intel/decisions.md
@.planning/intel/constraints.md
@.planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md
@manifest.json
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| extension manifest → Chrome runtime | Permissions block declares the API surface Chrome will grant; minimising this is the policy boundary |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-1-02 | Information Disclosure / over-privileged | `manifest.json` permissions array | mitigate | Drop `tabCapture` and `alarms` (no longer used); add `desktopCapture` (matches CONTEXT.md D-05 / D-A6). `host_permissions: [""]` and `activeTab` retained — already justified by Phase 3 screenshot path. Grep gate: `grep -v '^#' manifest.json \| grep -c '"tabCapture"' == 0 && grep -v '^#' manifest.json \| grep -c '"desktopCapture"' == 1` |
(T-1-01 codec downgrade and T-1-04 port-hijack are addressed in Plans 03 and 04. T-1-03 stream-content-leakage is an accepted residual risk per CONTEXT.md D-04 — recorded in Plan 03's threat model.)
Task 1: Amend intel/decisions.md DEC-003 and DEC-010 (D-A1)
.planning/intel/decisions.md
- .planning/intel/decisions.md (lines 48-60 DEC-003, lines 145-153 DEC-010)
- .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A1)
Append an Amendment block to DEC-003 immediately AFTER line 59 (the `Confirming source` line). The amendment block reads VERBATIM:
```markdown
## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-15)
- AMENDED-BY: Phase 01 CONTEXT.md D-01..D-05
- Replace `chrome.tabCapture.capture()` with `navigator.mediaDevices.getDisplayMedia()` called from the offscreen document.
- Offscreen document is created with `chrome.offscreen.Reason.DISPLAY_MEDIA` (replaces `USER_MEDIA`).
- Codec/bitrate/timeslice binding unchanged: `video/webm; codecs=vp9` @ 400 000 bps, MediaRecorder timeslice 2000 ms.
- Trade-off accepted: SPEC §1 "silent operation" is given up — Chrome's permanent "Sharing your screen" indicator is shown while recording. Phase 1 accepts this in exchange for broader capture coverage and elimination of `tabCapture` user-gesture juggling.
- Tab-switch re-attachment clause is REMOVED — `getDisplayMedia` captures a screen/window, not a tab. There is nothing to re-attach.
- Manifest permission `tabCapture` is REPLACED with `desktopCapture` (the latter is harmless: `getDisplayMedia` is a web standard API and does NOT actually require `desktopCapture`, but we declare it for clarity per CONTEXT.md D-05).
```
Then append an Amendment block to DEC-010 immediately AFTER line 152 (the `chrome.alarms` decision line). The amendment block reads VERBATIM:
```markdown
## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-15)
- AMENDED-BY: Phase 01 CONTEXT.md D-17..D-18
- Replace `chrome.alarms`-driven 20 s keepalive with a long-lived `chrome.runtime.connect` port opened from the offscreen document to the Service Worker. The port emits a `PING` message every 25 s; both directions of traffic reset the SW's 30 s idle timer per Chrome 110+ semantics (developer.chrome.com/blog/longer-esw-lifetimes).
- The `alarms` permission is removed from `manifest.json` (it is no longer used by Phase 1; Phase 2 / 3 may re-add if needed).
- Port lifetime cap (~5 minutes per Chromium-extensions community gist sunnyguan/f94058f66fab89e59e75b1ac1bf1a06e) is mitigated by reconnecting on `onDisconnect` and pre-emptively at ~290 s.
- See `.planning/phases/01-stabilize-video-pipeline/01-RESEARCH.md` Pattern 5 for the canonical implementation.
```
Do not modify any other text in decisions.md.
grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md
- `grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md` returns 2
- `grep -c "getDisplayMedia" .planning/intel/decisions.md` returns at least 1
- `grep -c "port" .planning/intel/decisions.md` returns at least 1
- DEC-003 still has its original text intact (the amendment is APPENDED, not replacing)
- DEC-010 still has its original text intact (the amendment is APPENDED, not replacing)
Both decisions carry an Amendment block; downstream phases that grep DEC-003 / DEC-010 now find the new contract alongside the original.
Task 2: Amend intel/constraints.md — retire two, add one (D-A2)
.planning/intel/constraints.md
- .planning/intel/constraints.md (lines 95-111 CON-tab-capture-binding and CON-service-worker-keepalive)
- .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A2)
Two edits, both in `.planning/intel/constraints.md`:
(1) Append a RETIRED block immediately AFTER line 102 (the end of CON-tab-capture-binding). VERBATIM text:
```markdown
### RETIRED (Phase 01-stabilize-video-pipeline, 2026-05-15)
- RETIRED-BY: Phase 01 CONTEXT.md D-01 / D-A2
- Reason: This phase replaces `chrome.tabCapture` with `navigator.mediaDevices.getDisplayMedia()`. The new API is not active-tab-bound; the recorder captures a screen / window selected once via Chrome's native picker and continues across tab switches.
- Replacement: CON-display-capture-binding (below).
```
(2) Append a RETIRED block immediately AFTER line 111 (the end of CON-service-worker-keepalive). VERBATIM text:
```markdown
### RETIRED (Phase 01-stabilize-video-pipeline, 2026-05-15)
- RETIRED-BY: Phase 01 CONTEXT.md D-17 / D-A2
- Reason: This phase replaces alarms-driven keepalive with a long-lived `chrome.runtime.connect` port between offscreen and Service Worker. Port-message traffic resets the SW idle timer per Chrome 110+ semantics.
- Replacement: CON-display-capture-binding (binds the port-keepalive expectations alongside the new capture API).
```
(3) Append a brand-new constraint immediately AFTER the existing `CON-buffer-storage` block (after line 196). VERBATIM text:
```markdown
---
## CON-display-capture-binding
- Source: Phase 01 CONTEXT.md D-01..D-17, RESEARCH.md Patterns 1 & 5
- Type: api-contract
- Constraint: Video capture uses `navigator.mediaDevices.getDisplayMedia()` invoked once per session from the offscreen document with `chrome.offscreen.Reason.DISPLAY_MEDIA`. The Service Worker is kept alive by a long-lived `chrome.runtime.connect({ name: 'video-keepalive' })` port opened by the offscreen, with traffic in both directions at a minimum cadence of 25 s and pre-emptive reconnect at 290 s.
- Replaces: CON-tab-capture-binding (RETIRED), CON-service-worker-keepalive (RETIRED).
- UX trade-off: Chrome's permanent "Sharing your screen" indicator is shown while recording. SPEC §1 silent-operation property is intentionally relaxed.
```
Do not modify any other text in constraints.md.
grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md
- `grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md` returns 2
- `grep -c "## CON-display-capture-binding" .planning/intel/constraints.md` returns 1
- The original `## CON-tab-capture-binding` and `## CON-service-worker-keepalive` headings still exist (RETIRED is APPENDED below them, not replacing them)
- `grep -c "video-keepalive" .planning/intel/constraints.md` returns at least 1
Two retired constraints carry RETIRED-BY markers; CON-display-capture-binding exists as the consolidated replacement constraint.
Task 3: Amend PROJECT.md Key Decisions table and Constraints section (D-A3)
.planning/PROJECT.md
- .planning/PROJECT.md (lines 79-110 Constraints section, lines 113-134 Key Decisions table)
- .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A3)
Three edits in `.planning/PROJECT.md`:
(1) DEC-003 row in the Key Decisions table — find line 124 starting `| **DEC-003**:` and replace the entire row with VERBATIM:
```markdown
| **DEC-003**: Active video via `getDisplayMedia()` (vp9 / 400 kbps / 2000 ms) | AMENDED by Phase 01: SPEC §2/§4.1/§7 originally specified `chrome.tabCapture`; Phase 01 swaps to `getDisplayMedia` invoked in the offscreen document with `chrome.offscreen.Reason.DISPLAY_MEDIA`. Codec/bitrate/timeslice binding unchanged. See `.planning/intel/decisions.md` DEC-003 Amendment. | — Pending | locked (Phase 1, post-Amendment) |
```
(2) DEC-010 row — find line 131 starting `| **DEC-010**:` and replace the entire row with VERBATIM:
```markdown
| **DEC-010**: Service Worker keepalive via long-lived port | AMENDED by Phase 01: SPEC §8 originally specified `chrome.alarms` at 20 s; Phase 01 swaps to a `chrome.runtime.connect` port between offscreen and SW with 25 s ping cadence and 290 s pre-emptive reconnect. See `.planning/intel/decisions.md` DEC-010 Amendment. | — Pending | locked (Phase 1, post-Amendment) |
```
(3) Constraints section — find line 100-101 (the `chrome.alarms` keepalive bullet) and replace with VERBATIM:
```markdown
- **Service Worker lifecycle**: MV3 SW unloads after ~30 s idle; a long-lived
`chrome.runtime.connect` port from offscreen to SW emits a PING every 25 s
to keep the SW alive (CON-display-capture-binding, AMENDED from
CON-service-worker-keepalive).
- **Tab capture binding**: REMOVED (CON-tab-capture-binding RETIRED). The new
`getDisplayMedia` binding (CON-display-capture-binding) is screen/window-
scoped, not tab-scoped, and survives tab switches without re-attach.
```
(this replaces the two bullets currently at lines 97-102 — the existing SW-keepalive bullet AND the existing tab-capture-binding bullet — with the two replacement bullets above. The bullet ordering and the rest of the Constraints section are unchanged.)
Do not modify any other text in PROJECT.md.
grep -c "AMENDED by Phase 01" .planning/PROJECT.md
- `grep -c "AMENDED by Phase 01" .planning/PROJECT.md` returns 2 (DEC-003 + DEC-010 rows)
- `grep -c "getDisplayMedia" .planning/PROJECT.md` returns at least 1
- `grep -c "long-lived port" .planning/PROJECT.md` returns at least 1
- `grep -c "RETIRED" .planning/PROJECT.md` returns at least 1
- The `## Key Decisions` heading and the `## Constraints` heading still exist
- All other rows of the Key Decisions table are untouched
PROJECT.md's Key Decisions table and Constraints section reflect the new contract; downstream readers of PROJECT.md see the amendments without needing to drill into intel/.
Task 4: Amend REQUIREMENTS.md REQ-video-ring-buffer (D-A4)
.planning/REQUIREMENTS.md
- .planning/REQUIREMENTS.md (lines 19-27 REQ-video-ring-buffer block)
- .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A4)
Replace the entire REQ-video-ring-buffer entry (currently lines 19-27 starting `- [ ] **REQ-video-ring-buffer**`) with VERBATIM:
```markdown
- [ ] **REQ-video-ring-buffer**: The extension maintains an in-memory ring
buffer containing the most recent 30 seconds of captured video. AMENDED in
Phase 01: video is acquired via `navigator.mediaDevices.getDisplayMedia()`
invoked from the offscreen document (with `chrome.offscreen.Reason.DISPLAY_MEDIA`),
NOT `chrome.tabCapture` as originally specified. The captured stream is
screen-or-window-scoped per the operator's one-time selection in Chrome's
native picker, and continues unchanged across tab switches. Encoding is
unchanged: `video/webm; codecs=vp9` @ 400 000 bps with a `MediaRecorder`
timeslice of 2000 ms; a single continuous recorder runs for the whole
session. The first emitted chunk (WebM header) is retained indefinitely;
subsequent chunks rotate out by the 30-second TTL rule. Bindings: DEC-003
(AMENDED), DEC-009, CON-video-window, CON-video-codec,
CON-webm-header-retention, CON-display-capture-binding (replaces RETIRED
CON-tab-capture-binding).
- SPEC §10 acceptance criteria: #2, #3, #7.
```
Do not modify any other text in REQUIREMENTS.md.
grep -c "AMENDED in" .planning/REQUIREMENTS.md
- `grep -c "AMENDED in" .planning/REQUIREMENTS.md` returns at least 1
- `grep -c "getDisplayMedia" .planning/REQUIREMENTS.md` returns at least 1
- `grep -c "active-tab video" .planning/REQUIREMENTS.md` returns 0 (the old "active-tab" wording is removed)
- `grep -c "REQ-video-ring-buffer" .planning/REQUIREMENTS.md` returns at least 2 (the bullet + the Traceability table row)
- The Traceability table row for REQ-video-ring-buffer remains intact
REQ-video-ring-buffer reads correctly for Phase 1 and binds the new constraint set.
Task 5: Amend ROADMAP.md Phase 1 description + Success Criterion #2 (D-A5)
.planning/ROADMAP.md
- .planning/ROADMAP.md (lines 25, 33-66 Phase 1 block)
- .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A5)
Two edits in `.planning/ROADMAP.md`:
(1) Replace line 25 VERBATIM with:
```markdown
- [ ] **Phase 1: Stabilize video pipeline** — Collapse offscreen duality, fix MediaRecorder shadow, fix WebM ring buffer playability, replace `chrome.tabCapture` with offscreen `getDisplayMedia` (AMENDED from original DEC-003)
```
(2) Replace Success Criterion #2 (currently lines 58-61 in the `**Success Criteria** (what must be TRUE):` block — "With the extension loaded and a tab open, a single continuous `MediaRecorder` ... without losing the WebM container header.") with VERBATIM:
```markdown
2. With the extension loaded and an operator session active, a single
continuous `MediaRecorder` is running on the operator-selected
screen/window source with timeslice 2000 ms; the recorder continues
unchanged across tab switches (no tab re-attach logic; AMENDED from the
original wording). The WebM container header is retained in the ring
buffer indefinitely.
```
Do not modify any other text in ROADMAP.md.
grep -c "AMENDED" .planning/ROADMAP.md
- `grep -c "AMENDED" .planning/ROADMAP.md` returns at least 2
- `grep -c "tab re-attach" .planning/ROADMAP.md` returns 0 (the old phrase is gone)
- `grep -c "getDisplayMedia" .planning/ROADMAP.md` returns at least 1
- Phases 2-5 sections remain untouched
ROADMAP.md Phase 1 description and Success Criterion #2 reflect the new contract; readers of ROADMAP.md see the amendment without needing to drill into CONTEXT.md.
Task 6: Manifest permission swap — tabCapture → desktopCapture, drop alarms (D-A6 / D-05)
manifest.json
- manifest.json (lines 6-14 permissions block)
- .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A6)
- .planning/phases/01-stabilize-video-pipeline/01-PATTERNS.md §`manifest.json` (lines 444-476)
Replace the `"permissions": [...]` array (currently lines 6-14) VERBATIM with:
```json
"permissions": [
"desktopCapture",
"activeTab",
"downloads",
"scripting",
"storage",
"offscreen"
],
```
Specifically:
- Replace `"tabCapture"` → `"desktopCapture"` (per D-A6).
- Remove `"alarms"` (the alarms keepalive is deleted in Plan 05; declaring an unused permission expands attack surface, mitigating T-1-02).
- Keep `"activeTab"` (needed for `chrome.tabs.captureVisibleTab` in Phase 3 screenshot path — current code uses it at src/background/index.ts:190).
- Keep `"downloads"` (used at src/background/index.ts:305).
- Keep `"scripting"` (content script injection).
- Keep `"storage"` (currently unused by code but retained per SPEC §7; do not remove in this phase — that is Phase 5 hardening territory).
- Keep `"offscreen"` (required to call `chrome.offscreen.createDocument`).
Do not modify any other field in `manifest.json` (manifest_version, name, version, description, host_permissions, background, content_scripts, action, icons all remain untouched).
node -e "const m=require('./manifest.json'); const p=m.permissions; if(p.includes('tabCapture')||p.includes('alarms')){process.exit(1)}; if(!p.includes('desktopCapture')||!p.includes('offscreen')){process.exit(2)}; console.log('ok')"
- `grep -c '"tabCapture"' manifest.json` returns 0
- `grep -c '"desktopCapture"' manifest.json` returns 1
- `grep -c '"alarms"' manifest.json` returns 0
- `grep -c '"offscreen"' manifest.json` returns 1
- `grep -c '"activeTab"' manifest.json` returns 1
- `node -e "require('./manifest.json')"` exits 0 (JSON valid)
- `host_permissions` array still contains `""` (unchanged)
manifest.json carries the final Phase-1 permissions set; downstream code-touching plans (02-07) operate against a manifest that matches the new API contract.
After all six tasks land:
1. `grep -c "AMENDED" .planning/PROJECT.md .planning/REQUIREMENTS.md .planning/ROADMAP.md` returns at least 4 lines (each file has at least one amendment marker; the precise totals are checked in each task's acceptance_criteria).
2. `grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md` returns 2.
3. `grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md` returns 2.
4. `grep -c '"tabCapture"' manifest.json` returns 0 and `grep -c '"desktopCapture"' manifest.json` returns 1.
5. `node -e "require('./manifest.json')"` exits 0 (JSON valid).
6. `node -e "require('./.planning/config.json')"` exits 0 (we did not touch config.json).
7. Every code-touching plan in Waves 0..2 can grep the doc baseline (manifest.json, PROJECT.md, REQUIREMENTS.md, ROADMAP.md, decisions.md, constraints.md) and find the amendments — no plan needs to back-patch a doc.
Commit cadence: ONE git commit per task (six atomic commits). Each commit message follows `docs(01-01): amend {file} per D-{ID}` style. The orchestrator handles commit creation.
- All six tasks complete and verified.
- No file in the dependency closure carries the pre-amendment wording.
- manifest.json `permissions` array exactly matches the list specified in Task 6's action block.
- Plans 02..07 can run against a self-consistent doc baseline (no late doc patches required mid-phase).