Files
mokosh/.planning/phases/01-stabilize-video-pipeline/01-01-PLAN.md

22 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, requirements_addressed, must_haves
phase plan type wave depends_on files_modified autonomous requirements requirements_addressed must_haves
01-stabilize-video-pipeline 01 execute 0
.planning/PROJECT.md
.planning/REQUIREMENTS.md
.planning/ROADMAP.md
.planning/intel/decisions.md
.planning/intel/constraints.md
manifest.json
true
REQ-video-ring-buffer
REQ-video-ring-buffer
truths artifacts key_links
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
path provides contains
.planning/PROJECT.md Amended DEC-003 / DEC-010 rows and amended Constraints section getDisplayMedia
path provides contains
.planning/REQUIREMENTS.md REQ-video-ring-buffer wording without 'active-tab' and bound to getDisplayMedia getDisplayMedia
path provides contains
.planning/ROADMAP.md Phase 1 Success Criterion #2 with tab-reattach clause removed MediaRecorder
path provides contains
.planning/intel/decisions.md DEC-003 and DEC-010 with Amendment blocks ## Amendment
path provides contains
.planning/intel/constraints.md RETIRED markers + new CON-display-capture-binding CON-display-capture-binding
path provides contains
manifest.json Permission swap desktopCapture
from to via pattern
manifest.json intel/constraints.md lockstep permission set desktopCapture
from to via pattern
intel/decisions.md PROJECT.md Amendment block referenced from Key Decisions table 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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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

<threat_model>

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: ["<all_urls>"] 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.) </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:

## 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:


## 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 <acceptance_criteria> - 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) </acceptance_criteria> 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:


### 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:


### 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:


---

## 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 <acceptance_criteria> - 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 </acceptance_criteria> 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:

| **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:

| **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:

- **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 <acceptance_criteria> - 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 </acceptance_criteria> 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:
- [ ] **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 <acceptance_criteria> - 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 </acceptance_criteria> 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:

- [ ] **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:

  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 <acceptance_criteria> - 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 </acceptance_criteria> 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:
  "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')" <acceptance_criteria> - 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 "<all_urls>" (unchanged) </acceptance_criteria> 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.

<success_criteria>

  • 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). </success_criteria>
After completion, create `.planning/phases/01-stabilize-video-pipeline/01-01-SUMMARY.md` with: - Which 6 files were amended and at which line ranges - The exact text of the new CON-display-capture-binding block (so downstream plans can quote it) - Confirmation of all six acceptance grep checks - Six commit SHAs (one per task)