Files
mokosh/.planning/ROADMAP.md
Mark 66e6f503a4 docs(01-12): state + roadmap + requirements — Plan 01-12 closure
Plan 01-12 closure documentation sync per the plan's Wave 7 Task 1
spec. Three docs land together as one atomic closure commit per the
Plan 01-13 + 01-14 closure cadence convention.

STATE.md changes:
- status: verifying → executing (Plan 01-10 welcome tab still pending)
- stopped_at: Plan 01-14 → Plan 01-12 closed via Wave 7 brand-fit
  ack 2026-05-20 'all good'; Plan 01-10 remains as final Phase 1
  functional plan
- last_updated + last_activity: 2026-05-19 → 2026-05-20
- progress.completed_plans: 12 → 13 (13 plans: 01-01..01-09 + 01-11
  spike + 01-12 + 01-13 + 01-14; Plan 01-10 pending)
- progress.percent: 86 → 93
- "Plan 01-13 closure" header annotated: brand/design ack subsequently
  closed via Plan 01-12 Wave 7 2026-05-20
- New "Plan 01-12 closure (2026-05-20)" section: 7-wave execution arc
  with all 10 commit hashes (3fe018b plan-baseline-revision → 34a9ce1
  Wave 0 → f86fd60 + abab6e1 Wave 1 → 7732a30 Wave 2 → 110cebc Wave 3
  → 468f16d Wave 4 → e8d2881 Wave 5 → b909c37 Wave 6 + 865d394
  pre-checkpoint + f319c7d SUMMARY); R2 Lora substitution; 16 i18n
  keys; branded icons; BADGE_REC_COLOR #b2543d; chrome.i18n fallback;
  A18-A22 harness; pre-checkpoint bundle gates per
  feedback-pre-checkpoint-bundle-gates.md; setimmediate polyfill
  discovery logged to deferred-items.md (Phase 5 hardening); operator
  brand-fit ack; Plan 01-13 Task 9 functional closure
- "Outstanding Phase 1 gates" updated: Plan 01-13 Task 9 + Plan 01-12
  CLOSED; only Plan 01-10 remains
- Performance Metrics table: Plan 01-12 entry appended (~10h
  cumulative; 10 tasks; ~50+ files)
- Decisions section: 2 new entries for Plan 01-12 design integration
  + Plan 01-13 Task 9 closure linkage
- Session Continuity: last/prior session updated to 2026-05-20 /
  2026-05-19

ROADMAP.md changes:
- Phase 1 Plans list: 01-12-PLAN.md entry flipped from [ ] to [x]
  with full closure annotation (R2 Lora self-host, tokens.css
  canonical, 16 i18n keys across en+ru, branded Loom icons, manifest
  i18n, BADGE_REC_COLOR madder #b2543d, chrome.i18n fallback,
  harness A18-A22, operator brand-fit ack 2026-05-20 'all good')
- Phase 1 plan count: 13 → 14 plans (01-01 through 01-14)
- Progress table Phase 1 row: 7/7 Complete → 13/14 Executing with
  closure-status disambiguation (functional via Plan 01-13;
  design/brand via Plan 01-12; Plan 01-10 welcome tab remains)

REQUIREMENTS.md changes:
- REQ-install-clean: [ ] Pending → [x] Complete (2026-05-20)
  with annotation: fresh build clean; zero remote-font CSP errors;
  branded icons; en+ru manifest:name resolution; operator brand-fit
  ack
- REQ-manifest-permissions: [ ] Pending → [x] Complete (2026-05-20)
  with annotation: manifest:name + :description +
  :action.default_title migrated to __MSG_*__ + default_locale='en';
  manifest validation PASS; en↔ru parity; permissions DEC-011
  baseline UNCHANGED
- Traceability table: both requirements moved from Phase 3/4 Pending
  to "Phase 1 closure via Plan 01-12" Complete
- Footer: last-updated 2026-05-15 → 2026-05-20 with annotation
  noting the requirements flipped at Plan 01-12 closure

No code changes; pure documentation closure sync.

Closure commit hashes:
- SUMMARY: f319c7d (.planning/phases/01-stabilize-video-pipeline/01-12-SUMMARY.md)
- State sync: this commit (.planning/STATE.md + .planning/ROADMAP.md
  + .planning/REQUIREMENTS.md)

Phase 1 status post-closure:
- Functional contract: CLOSED via Plan 01-13 harness PASS (2026-05-19)
- Design/brand contract: CLOSED via Plan 01-12 brand-fit ack
  (2026-05-20)
- Remaining: Plan 01-10 (welcome tab) — operator-facing onboarding
  surface; canonical src/shared/tokens.css from Plan 01-12 now
  available for swap-in

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:33:52 +02:00

250 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roadmap: Mokosh
## Overview
The Mokosh codebase is a partially-broken first attempt at SPEC `Тз расширение
фаза1.md`. An external audit identified 7 P0 defects that prevent SPEC §10
acceptance. This roadmap therefore frames Phase 1 as **stabilization to spec**,
not greenfield: phases 13 each remediate a tightly-grouped subset of the P0
defects along sensible commit boundaries; phase 4 runs the SPEC §10 smoke pass
end-to-end. An optional phase 5 absorbs the P1/P2 follow-ups (SW state
persistence, `fetch` interception fix, `meta.json` field hardening,
`generate-icons.js` ESM/CJS, dead-code cleanup).
The journey: **broken-but-installable → playable video → masked DOM + log →
working export → green §10 smoke → harden + clean up**.
## Phases
**Phase Numbering:**
- Integer phases (1, 2, 3): Planned milestone work.
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED).
Decimal phases appear between their surrounding integers in numeric order.
- [ ] **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). **Closed 2026-05-15 then REOPENED 2026-05-16**: the 2026-05-15 closure was based on insufficient operator playback verification; D-13's concat-of-self-contained-segments architecture produces a multi-EBML WebM that plays only ~9 s instead of ~30 s in standards-compliant parsers (mpv, ffmpeg, Chrome HTMLMediaElement). UAT Test 3 retest on 2026-05-16 confirmed via byte-level EBML probe. SPEC §10 #7 not actually satisfied. Plan 01-08 (WebM remux via ts-ebml + webm-muxer) replaces `mergeVideoSegments`'s file-concat with a real single-EBML remux. See `.planning/debug/d13-multi-ebml-concat-unplayable.md`. Option C port-lifecycle refactor (debug session `empty-archive-port-race`) DID land cleanly and is retained. Phase 1 will additionally absorb whole-desktop + auto-start UX work (Plans 01-09/01-10) per the 2026-05-16 amended charter.
- [ ] **Phase 2: Stabilize DOM + event capture privacy** — Migrate rrweb to v2 `maskInputFn`, plug `content/index.ts setupInputLogging` password leak
- [ ] **Phase 3: Stabilize export pipeline** — Restore user-activation gesture in popup, delete dead `permissions.request`, replace base64 `data:` URL with Blob URL minted in offscreen
- [ ] **Phase 4: SPEC §10 smoke verification** — End-to-end install-and-record-and-export pass against all 9 acceptance criteria
- [ ] **Phase 5: Harden + clean up** _(optional)_ — P1/P2 follow-ups: SW state persistence, fetch interception, `meta.json` fields, `generate-icons.js` ESM/CJS, dead-code
## Phase Details
### Phase 1: Stabilize video pipeline
**Goal**: The video ring buffer captures the most recent 30 s of the active
tab's video continuously across tab switches, with a playable WebM header
retained — so that on export the assembled `last_30sec.webm` will play.
**Depends on**: Nothing (first phase). Operates on the existing `offscreen/`
directory + `vite.config.ts` inline string + `src/background/`.
**Requirements**: REQ-video-ring-buffer
**P0 defects addressed**:
- P0-1: Collapse the offscreen duality (`offscreen/index.ts` + inline string in
`vite.config.ts`) into a single source of truth; fix the `mediaRecorder`
shadow that breaks `stopRecording`.
- P0-2: Fix WebM ring buffer playability — single continuous `MediaRecorder`,
2000 ms timeslice per spec (CON-video-codec), cluster-timestamp-based rolling
window honouring the WebM header retention rule (DEC-009).
- P0-3: Make capture always-on with `chrome.tabs.onActivated` /
`chrome.tabs.onUpdated` re-attachment; start on `onInstalled` / `onStartup`,
not on popup open (CON-tab-capture-binding, REQ-video-ring-buffer).
**Success Criteria** (what must be TRUE):
1. [x] There is exactly one source of truth for the offscreen document; rebuilding
`vite.config.ts` does not regenerate a divergent inline duplicate, and
`stopRecording` runs without `mediaRecorder is undefined` shadow errors.
2. [x] With the extension loaded and an operator session active, a `MediaRecorder`
is running on the operator-selected screen/window source. AMENDED 2026-05-15
(D-13 fix-a3 activation): the recorder cycles in 10 s self-contained segments
(stop+restart on the same `MediaStream`) instead of a single continuous
recorder — replaces D-09..D-11 to fix VP9 keyframe orphan-P-frame freezes.
Recording continues unchanged across tab switches (no tab re-attach logic;
AMENDED from the original wording).
3. [x] The in-memory video ring buffer at any instant contains at most 3 of the
most recent 10 s WebM segments (3 × 10 s = 30 s window, no more, no less);
concatenating segments sequentially yields a multi-EBML-header WebM that
Chrome plays end-to-end (SPEC §10 #7 — operator confirmed clean playback
2026-05-15; ffmpeg `-v warning -i fixture -f null -` exit 0 with zero
decoder errors, only expected muxer DTS-monotonicity warnings at segment
join boundaries).
**Plans**: 14 plans (01-01 through 01-14). Plan 01-10 (welcome tab) is the final remaining functional plan; all other plans complete.
- [x] 01-01-PLAN.md — Doc cascade: amend DEC-003 / DEC-010 / RETIRE constraints / swap manifest permissions (D-A1..D-A6)
- [x] 01-02-PLAN.md — Wave-0 test infrastructure: Vitest install + 4 RED test files + fixtures placeholder
- [x] 01-03-PLAN.md — Offscreen recorder TDD: ring buffer + codec strict-mode + getDisplayMedia + track-ended cleanup; D-13 fallback skeleton pre-staged
- [x] 01-04-PLAN.md — Port keepalive + OFFSCREEN_READY handshake (TDD): replaces alarms keepalive on offscreen side
- [x] 01-05-PLAN.md — SW shrink: delete legacy buffer + alarms + IndexedDB + tabCapture paths; wire SW-side onConnect host
- [x] 01-06-PLAN.md — Build pipeline collapse: delete vite.config.ts inline plugin + top-level offscreen/ dir; declare rollupOptions.input
- [x] 01-07-PLAN.md — Manual smoke + ffprobe D-12 acceptance gate + A3 empirical-playback gate; D-12 + A3 debug sessions resolved mid-execution via pre-staged base64 wire format + D-13 restart-segments; regression fixture committed; SPEC §10 #2/#3/#7 functionally green (Closed 2026-05-15)
- [x] 01-08-PLAN.md — WebM remux via ts-ebml + webm-muxer (replaces D-13 file-concat; closes SPEC §10 #7 playability per debug d13-multi-ebml-concat-unplayable.md)
- [x] 01-09-PLAN.md — Toolbar onClicked direct flow + monitor-only picker + onStartup notification + 3-state badge state machine; closure-by-harness Amendment 2 (Plan 01-13 PASS substitutes for operator UAT)
- [ ] 01-10-PLAN.md — Welcome tab (Hero + Loom dial per D-02; first-install onboarding; harness A15-A17)
- [x] 01-11-PLAN.md — UAT harness Approach-A spike (PIVOTED to 01-13; carries forward Wave 0 infrastructure + Tier-1 grep gate; falsified hypotheses recorded)
- [x] 01-12-PLAN.md — Design integration (R2 Lora self-host, src/shared/tokens.css canonical, 16 i18n keys across en+ru, branded Loom icons replacing Bug A placeholders, manifest i18n + default_locale='en', BADGE_REC_COLOR madder #b2543d, chrome.i18n.getMessage with `|| <const>` fallback, harness A18-A22; operator brand-fit ack 2026-05-20 'all good')
- [x] 01-13-PLAN.md — UAT harness via Approach B (extension-internal-page driver + offscreen synthetic stream; 15/15 GREEN; Plan 01-09 functional closure)
- [x] 01-14-PLAN.md — Picker narrowing via monitorTypeSurfaces:'include' (Chrome 119+ picker enhancement; A23 harness regression)
### Phase 2: Stabilize DOM + event capture privacy
**Goal**: rrweb captures DOM events on typical pages and the user-event log
captures clicks/navigation/network errors — and in neither stream do password
values appear.
**Depends on**: Phase 1 (no functional dependency, but Phase 1 establishes the
"capture is always-on" baseline that this phase plugs into).
**Requirements**: REQ-rrweb-dom-buffer, REQ-user-event-log,
REQ-password-confidentiality
**P0 defects addressed**:
- P0-5: rrweb data-sensitive leak — migrate to rrweb v2 `maskInputFn` (the
legacy `maskInputSelector` is gone in v2.0.0-alpha.4 per `package.json`); fix
the parallel leak in `src/content/index.ts setupInputLogging` so password
field values are dropped at logger entry, not just at rrweb level.
**Success Criteria** (what must be TRUE):
1. On a page containing `<input type="password">` and elements with
`data-sensitive="true"`, rrweb snapshots for that page mask the value of
both kinds of fields (verified by inspecting exported `rrweb/session.json`).
2. On the same page, typing into a password field produces no `input` event
entry containing the typed value in the user-event log
(`logs/events.json`).
3. On a typical page with forms, tables, and a modal, rrweb records DOM
events without throwing in the Content Script console; the event log
captures clicks, navigations (`popstate`/`hashchange`), and network errors
(`fetch` / `XHR` >= 400).
**Plans**: TBD
**UI hint**: yes
### Phase 3: Stabilize export pipeline
**Goal**: A click on "Сохранить отчёт об ошибке" produces a SPEC-conformant ZIP
archive on disk in under 5 s, containing a screenshot taken at click time,
laid out per CON-archive-layout, with `meta.json` per CON-meta-json-schema, and
declared by a manifest carrying exactly the permission set in DEC-011.
**Depends on**: Phase 1, Phase 2 (export consumes the video + rrweb + event-log
buffers established by phases 1 and 2).
**Requirements**: REQ-popup-ui, REQ-screenshot-on-export, REQ-archive-layout,
REQ-meta-json-schema, REQ-archive-export-latency, REQ-manifest-permissions
**P0 defects addressed**:
- P0-4: Restore the user-activation gesture for `getMediaStreamId` by moving
the call to the popup-click handler; delete the dead `permissions.request`
dance that was masking the missing gesture (REQ-popup-ui,
CON-tab-capture-binding).
- P0-6: Replace the base64 `data:` URL download with a Blob URL minted in the
offscreen document — the Service Worker lacks `URL.createObjectURL` (DEC-006,
REQ-archive-export-latency).
**Success Criteria** (what must be TRUE):
1. Opening the popup shows a button reading "Сохранить отчёт об ошибке" with
sub-label "Последние 30 сек видео + 10 мин лога"; clicking it transitions
idle → "Сохраняю..." → "Готово! ✓" → idle (with 3 s revert) and triggers
a `chrome.downloads` download.
2. The downloaded file lands in the user's Downloads folder, named
`session_report_YYYY-MM-DD_HH-MM-SS.zip`, in under 5 seconds from click;
opening it reveals exactly the layout in REQ-archive-layout
(`video/last_30sec.webm`, `rrweb/session.json`, `logs/events.json`,
`screenshot.png`, `meta.json` at the root) with no extra entries.
3. `meta.json` validates against the verbatim CON-meta-json-schema (all 7
fields present, types correct, `timestamp` is ISO-8601 with `Z`).
4. `manifest.json` in `dist/` after `npm run build` declares exactly the
permission set in DEC-011 with no additional or missing entries; loading
unpacked into Chrome produces no permission-related warnings or errors in
`chrome://extensions/`.
**Plans**: TBD
**UI hint**: yes
### Phase 4: SPEC §10 smoke verification
**Goal**: All 9 SPEC §10 acceptance criteria pass against an unpacked load of
the build into a real Chrome instance.
**Depends on**: Phase 1, Phase 2, Phase 3.
**Requirements**: REQ-install-clean (and end-to-end verification of all
preceding REQs)
**P0 defects addressed**:
- P0-7: End-to-end smoke verification against §10. This is a verification
phase, not a new implementation — it confirms the cumulative output of
phases 13 actually satisfies the SPEC.
**Success Criteria** (what must be TRUE):
1. The extension installs into Chrome via "Load unpacked" against `dist/`
with no errors or warnings in `chrome://extensions/`.
2. With the extension loaded and a normal browsing session under way, the
video buffer runs continuously across tab switches and never holds more
than 30 s of footage (confirmed by inspecting the SW console / a debug
export).
3. On a typical page (form + table + modal) rrweb records without throwing,
the event log captures clicks/navigation/network errors, and passwords
are absent from both streams.
4. A click on the popup button produces a ZIP in Downloads in under 5 s; the
ZIP opens; `video/last_30sec.webm` plays in a browser.
5. Background RAM consumption (measured via Chrome Task Manager) does not
exceed 50 MB during a sustained recording session (CON-ram-ceiling).
**Plans**: TBD
### Phase 5: Harden + clean up _(optional)_
**Goal**: Eliminate the P1/P2 follow-ups identified in the audit so that the
codebase is not just spec-conformant but maintainable. This phase has no new
v1 requirements — it improves robustness and removes technical debt around
already-shipped behaviour.
**Depends on**: Phase 4 (do not harden until §10 is green).
**Requirements**: none (no new v1 REQs; all v1 REQs are covered by phases 14)
**P1/P2 items addressed** (informative list from the audit, exact scope
finalized at plan time):
- SW state persistence around the 30 s idle unload edge cases.
- `fetch` interception fix in the network-error path of REQ-user-event-log.
- `meta.json` field hardening (timestamp source, version source, totalEvents
derivation).
- `generate-icons.js` ESM/CJS compatibility with the rest of the toolchain.
- Dead-code cleanup (the `permissions.request` dance removed in Phase 3 may
have stranded helpers; the offscreen duality removed in Phase 1 may have
stranded shims).
- `getDisplayMedia` cursor visibility constraint (`video: { cursor: 'always' }`)
— refines capture quality for diagnostic UX; surfaced during Phase 1 smoke
(2026-05-15) as a user observation. Operator's screen cursor was absent
from captured frames despite being the highest-signal cue when reproducing
pointer-driven bugs. Constraint is opt-in per the `getDisplayMedia` spec
and Chrome implements it via the `CursorCaptureConstraint` enum (`always`
/ `motion` / `never`).
**Success Criteria** (what must be TRUE):
1. After running the extension idle for >5 minutes, then exporting, the
archive still contains a non-empty video buffer (proves SW state
persistence works across one or more SW unload/reload cycles).
2. A page that issues a failing `fetch` (response code >= 400) produces a
`network_error` entry in `events.json`; a failing `XMLHttpRequest` does
too.
3. `npm run build` and `node generate-icons.js` both succeed under the
project's module setting (`"type": "module"` in `package.json`) with no
`require is not defined` or `Cannot use import statement outside a
module` errors.
4. A repo grep for the symbols deleted in phases 1 and 3
(`permissions.request`, the duplicate offscreen inline string) returns no
live references.
**Plans**: TBD
## Progress
**Execution Order:**
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5.
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Stabilize video pipeline | 13/14 | Executing (Plan 01-10 welcome tab pending) | Functional contract closed 2026-05-19 via Plan 01-13 harness PASS; design/brand contract closed 2026-05-20 via Plan 01-12 brand-fit ack |
| 2. Stabilize DOM + event capture privacy | 0/TBD | Not started | - |
| 3. Stabilize export pipeline | 0/TBD | Not started | - |
| 4. SPEC §10 smoke verification | 0/TBD | Not started | - |
| 5. Harden + clean up (optional) | 0/TBD | Not started | - |