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:
@@ -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)*
|
||||
|
||||
Reference in New Issue
Block a user