docs(01): add Plans 01-08 / 01-09 / 01-10 (amended Phase 1 charter)

Plans cover the post-D-13 architecture and the auto-start UX charter
expansion that landed during 2026-05-16 UAT:

- Plan 01-08 — WebM remux via ts-ebml@3.0.2 + webm-muxer@5.1.4. Replaces
  the broken file-concat in mergeVideoSegments with a real single-EBML
  remux. Drives the 2 RED tests in tests/offscreen/webm-playback.test.ts
  to GREEN. Regenerates the canonical fixture against the remuxer.
  5 tasks (4 TDD + 1 operator empirical checkpoint), wave 1.

- Plan 01-09 — Whole-desktop constraint (displaySurface:'monitor',
  cursor:'always') + post-grant validation, chrome.action.onClicked
  direct toolbar invocation, chrome.action badge state machine
  (REC/OFF/ERROR), chrome.runtime.onStartup notification + recovery
  notification on onUserStoppedSharing, popup scoped to SAVE-only.
  17 new test assertions across 4 test files. smoke.sh updated to
  auto-select an entire screen. 5 tasks (4 TDD + 1 operator empirical
  checkpoint), wave 2 (depends on 01-08).

- Plan 01-10 — chrome.runtime.onInstalled welcome tab on first install
  via chrome.storage.local guard; vanilla welcome.html/ts/css bundle
  with single "Начать запись" button consuming install-time activation.
  Uses centralized Logger pattern. 4 tasks (3 TDD + 1 operator empirical
  checkpoint), wave 3 (depends on 01-09).

CONTEXT.md amendment block appended with 4 disambiguated decisions:
- D-14-remux: WebM remux supersedes D-13 file-concat
- D-15-display-surface: whole-desktop + cursor visibility lifted from
  Phase 5 deferral
- D-16-toolbar: toolbar onClicked + popup SAVE-only + badge state
  machine + onStartup/recovery notifications
- D-17-onboarding: welcome tab on first install (distinct from
  D-17-port-lifecycle from Option C)
The earlier D-17 port-lifecycle heading also renamed to hyphenated
form for cross-ref consistency.

Plan-check loop: 3 iterations (initial + 2 revisions). Iteration 1
surfaced 11 findings (2 BLOCKER + 6 WARNING + 3 INFO); all addressed
via revision iter 1 with checker-recommended fixes. Iteration 2
surfaced 3 derivative regressions (literal-string grep anchors from
the iter-1 fixes did not match live CONTEXT.md); all addressed in iter
2 with empirically-validated literals. Iteration 3 PASSED clean.

Validation: gsd-sdk frontmatter.validate + verify.plan-structure both
return valid=True for all 3 plans. Plan 01-08 Task 4 verify-chain
grep tested end-to-end against live CONTEXT.md (exit 0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 09:19:22 +02:00
parent bc310d98cf
commit 2e499d7387
4 changed files with 1849 additions and 2 deletions

View File

@@ -299,7 +299,7 @@ phase, so downstream phases see a consistent baseline:
---
## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-16) — D-17 port lifecycle
## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-16) — D-17-port-lifecycle
- **AMENDED-BY:** debug session `empty-archive-port-race` (Option C, resolved 2026-05-16)
- **Replaces nothing.** D-17 above stands as the original port-keepalive
@@ -367,6 +367,211 @@ phase, so downstream phases see a consistent baseline:
---
## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-16 second batch) — Plans 01-08 / 01-09 / 01-10 charter
**Context.** On 2026-05-16 Phase 1 was REOPENED after UAT Test 3 re-attempt
revealed two compounding gaps in the 2026-05-15 closure:
1. D-13's concat-of-self-contained-WebM-segments architecture produces a
multi-EBML-header file that mpv, Chrome's HTMLMediaElement, and
ffprobe's `format=duration` all play as ~9.94 s (the first segment's
Info.Duration only) instead of ~30 s. The "operator-confirmed clean
Chrome playback" check from 2026-05-15 verified playback ran without
freezing but did not measure total duration. SPEC §10 #7
(`last_30sec.webm plays back in a browser`) is not actually satisfied.
See `.planning/debug/d13-multi-ebml-concat-unplayable.md` for the
byte-level EBML probe + library-survey + decision rationale.
2. The operator UX is unsatisfactory at the picker stage (tab-share
footgun: "Share this tab instead" affordance) and at the activation
stage (3 clicks per session: toolbar → popup open → Start button →
picker). These were flagged in UAT Test 3's advisory section and
deferred to Phase 5 at closure time; the reopen reclassifies them
as Phase 1 deliverables because the same fix-cycle window is
available.
The orchestrator's "add-more-plans" routing on 2026-05-16 adds three
plans (01-08, 01-09, 01-10) to close these gaps without re-litigating
the seven plans (01-01..01-07) that already landed.
**D-14-remux: WebM remux via ts-ebml + webm-muxer (supersedes D-13's file-concat).**
- **AMENDED-BY:** debug session `d13-multi-ebml-concat-unplayable`
(root-cause confirmed 2026-05-16). Remediation lands via Plan 01-08.
- **Replaces partial.** D-13's recorder-side restart-segments lifecycle
(the part that fixed the orphan-P-frame freeze observed in debug
session `webm-playback-freeze`) is PRESERVED. Only D-13's file-concat
merge on the SW save path is retired. Original D-13 text above
remains intact for provenance.
- **Architectural commitments retired:** D-13 file-concat byte-stream
merge in `src/background/index.ts:mergeVideoSegments` is RETIRED.
Concat of self-contained WebM segments does NOT produce a single
playable 30 s WebM in any common consumer-grade player.
- **Architectural commitments added:**
- `remuxSegments(segments: VideoSegment[]): Promise<Blob>` in
`src/background/webm-remux.ts` replaces `mergeVideoSegments`.
Parses each segment via `ts-ebml`'s Decoder, walks the EBML tree,
extracts each Cluster's SimpleBlock children (VP9 frame bytes +
keyframe flag + cluster Timecode + block offset), and re-mixes
into a single output WebM via `webm-muxer`'s `Muxer<ArrayBufferTarget>.addVideoChunkRaw(data, type, timestampUs)`.
Each frame's timestamp is adjusted to be monotonic against a
single global timeline.
- **Library choice (LOCKED):** `ts-ebml` ^3.0.2 (parse) +
`webm-muxer` ^5.1.4 (write). Both MIT, both actively maintained
(last releases 2025-09-28 and 2025-07-02 respectively), both
SW-compatible (no DOM globals on the hot path). Combined gzipped
weight ~100 KB. Verified by `tests/background/webm-remux-deps.test.ts`.
- **`createArchive` becomes async on the merge path.**
`videoBlob = await remuxSegments(...)` replaces synchronous
`mergeVideoSegments(...)`. The existing `EmptyVideoBufferError`
throw (Option C, D-17-port-lifecycle amendment) is preserved AND extended to
fire on a zero-byte remux output.
- **D-13 recorder-side lifecycle UNCHANGED.** The offscreen-side
`src/offscreen/recorder.ts` keeps the restart-segments rotation
(`SEGMENT_DURATION_MS = 10_000` ms, `MAX_SEGMENTS = 3`), which
remains the canonical fix for the orphan-P-frame freeze from
debug session `webm-playback-freeze`. The remux happens only on
export, only in the SW.
- **Pinning contracts added (Plan 01-08):**
- `tests/background/webm-remux-deps.test.ts` (2 tests — library
presence + SW-compat).
- `tests/background/webm-remux.test.ts` (5 tests — single-EBML
invariant, monotonic timestamps, size sanity, ffprobe duration
>= 25 s, frame-count tolerance).
- `tests/offscreen/webm-playback.test.ts` lines 231-316 (the 2 RED
tests landed by the d13 debug session) flip GREEN against the
regenerated `tests/fixtures/last_30sec.webm`.
- **D-13 status:** PARTIALLY RETIRED. Recorder lifecycle preserved;
file-concat merge retired. CONTEXT.md D-13 text above stands for
historical provenance; downstream readers reaching D-13 must ALSO
read this D-14-remux amendment.
**D-15-display-surface: Whole-desktop constraint + post-grant validation (replaces
operator-discretion picker).**
- **AMENDED-BY:** UAT Test 3 advisory finding (2026-05-16): "Share
this tab instead" Chrome affordance is a one-click footgun that
redirects the recording target mid-session. Reclassified from
Phase 5 advisory to Phase 1 deliverable via Plan 01-09.
- **Replaces nothing structurally.** D-01's `getDisplayMedia` choice
stands; this amendment narrows the constraints object passed to it.
- **Architectural commitments added:**
- `getDisplayMedia` is invoked with constraints
`{video: { displaySurface: 'monitor', cursor: 'always' }, audio: false}`.
The `displaySurface: 'monitor'` constraint hints Chrome's picker
to default to entire-screen selection; the `cursor: 'always'`
constraint includes the operator's screen cursor in captured
frames (this lifts the Phase 5 cursor-visibility refinement
opportunistically — STATE.md Decisions entry [Phase 01-07-deferred-to-5]).
- Post-grant validation reads `track.getSettings().displaySurface`.
If the operator overrode the hint and picked a tab or window,
the recorder tears down the stream, nulls `mediaStream`, and
throws `Error('wrong-display-surface: got "<observed>"')`. The
throw routes through `classifyCaptureError` (extended with a
`'wrong-display-surface'` branch added to the `CaptureErrorCode`
union) into the existing `RECORDING_ERROR` channel.
- The `CaptureErrorCode` union grows by one member:
`'wrong-display-surface'` (joins the existing seven).
- **Pinning contract added (Plan 01-09):**
- `tests/offscreen/display-surface-constraint.test.ts` (4 tests:
constraints applied; wrong-displaySurface emits RECORDING_ERROR;
monitor-displaySurface does NOT emit; classifyCaptureError
branch).
**D-16-toolbar: Toolbar-onClicked + popup-SAVE-only + badge state machine +
onStartup/recovery notifications (replaces popup-Start-button activation).**
- **AMENDED-BY:** UAT Test 3 ergonomics observation; operator-experience
goal "per-session click count from 3 to 2" surfaced by user on
2026-05-16. Reclassified from soft-deferred UX-polish to Phase 1
deliverable via Plan 01-09.
- **Replaces nothing structurally.** The existing popup → SW
`REQUEST_PERMISSIONS` → `startVideoCapture` flow stands; this
amendment changes WHICH UI surface triggers REQUEST_PERMISSIONS.
- **Architectural commitments added:**
- `chrome.action.onClicked` listener registered at SW module load.
When `isRecording === false`, the handler invokes
`startVideoCapture()` directly (the toolbar click counts as the
user gesture for `getDisplayMedia`).
- Dynamic `chrome.action.setPopup` swap: empty string (`''`) in
OFF mode (so toolbar click triggers `onClicked`); pointing to
`src/popup/index.html` in REC mode (so toolbar click opens the
popup for SAVE). The SAVE-only popup retires the auto-prompt
behavior in `src/popup/index.ts:checkPermissions`.
- Badge state machine with three states. REC: green background
(`#00C853`), text `'REC'`, tooltip "Recording — last 30 s
buffered. Click to save." OFF: red background (`#D32F2F`),
empty text, tooltip "Not recording. Click to start." ERROR:
yellow background (`#F9A825`), text `'ERR'`, tooltip
"Recording error. Click to try again."
- `chrome.runtime.onStartup` fires `chrome.notifications.create`
once per browser startup with a `'mokosh-startup-'`-prefixed id,
inviting the operator to click to start.
- `chrome.notifications.onClicked` validates the id prefix
(`'mokosh-'`), drains the notification via `chrome.notifications.clear`,
and triggers `startVideoCapture()` (notification click is also
a user gesture under Chrome's documented activation model).
- On `RECORDING_ERROR` receipt, the SW transitions badge to ERROR
and emits a `'mokosh-recovery-'`-prefixed recovery notification
inviting a fresh start.
- `manifest.json` adds `notifications` to permissions. `default_popup`
retained for the SAVE flow.
- **Popup role change.** `src/popup/index.ts` no longer auto-requests
permissions on init. `checkPermissions` / `requestPermissions`
functions are removed from the init path. Empty-state copy updated
to direct operator to the toolbar icon.
- **Smoke harness update.** `smoke.sh`'s auto-select target string
changes from the tab title to an entire-screen string (e.g.
`"Entire screen"` or `"Screen 1"` depending on Chrome locale). If
the auto-select fails under a non-English Chrome, a one-time
manual pick is documented as the fallback.
- **Pinning contracts added (Plan 01-09):**
- `tests/background/toolbar-action.test.ts` (4 tests: onClicked
routing; setPopup dance).
- `tests/background/badge-state-machine.test.ts` (4 tests: three
badge states + RECORDING_ERROR transition).
- `tests/background/onstartup-notification.test.ts` (4 tests:
onStartup → create notification; onClicked → start recording;
RECORDING_ERROR → recovery notification).
**D-17 (NEW — distinct from the port-lifecycle D-17 amendment above):
Onboarding welcome tab on first install.**
> NB: this is a SEPARATE D-17 marker, scoped to Plan 01-10. The earlier
> D-17 amendment block (port lifecycle) is also labeled D-17 in its
> historical context (it amends the original D-17 from the 2026-05-15
> decisions block above). To disambiguate downstream readers, this
> Plan 01-10 marker is referenced as D-17-onboarding in cross-citations;
> the port-lifecycle marker is referenced as D-17-port-lifecycle.
- **Trigger:** Plan 01-09's UX is operator-facing at runtime; Plan 01-10
adds the install-time activation path so the operator's very first
interaction with the extension is also one-click.
- **Architectural commitments added:**
- `chrome.runtime.onInstalled` handler extended: on
`details.reason === 'install'` AND `chrome.storage.local`
`'onboarding-completed'` flag absent, open
`src/welcome/welcome.html` via
`chrome.tabs.create({url: chrome.runtime.getURL(...)})`. After the
tab opens, set the `'onboarding-completed'` flag so future installs
or reloads do NOT re-open the welcome.
- New welcome page bundle: `src/welcome/welcome.{html,ts,css}`.
Vanilla DOM per project style; single `'Начать запись'` button
that sends `REQUEST_PERMISSIONS` (the install-time click counts
as user gesture for `getDisplayMedia`).
- `manifest.json` adds `web_accessible_resources` array with entry
for `src/welcome/welcome.html` so `chrome.runtime.getURL` resolves.
`'storage'` permission already present (no change needed).
- `vite.config.ts` `rollupOptions.input` gains a `welcome:
'src/welcome/welcome.html'` entry so crxjs bundles the page.
- **Pinning contract added (Plan 01-10):**
- `tests/background/onboarding.test.ts` (3 tests: first install
creates welcome tab; subsequent install does NOT; already-completed
flag suppresses).
---
*Phase: 01-stabilize-video-pipeline*
*Context gathered: 2026-05-15*
*Amended: 2026-05-16 (debug session empty-archive-port-race, Option C)*
*Amended: 2026-05-16 (debug session empty-archive-port-race, Option C — D-17-port-lifecycle narrowing)*
*Amended: 2026-05-16 (Plans 01-08 / 01-09 / 01-10 charter — D-14-remux WebM remux supersedes D-13 file-concat; D-15-display-surface whole-desktop + cursor; D-16-toolbar toolbar + badge + notifications; D-17-onboarding welcome tab)*