# 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 framed Phase 1 as **stabilization to spec**, not
greenfield: phases 1–2 each remediate a tightly-grouped subset of the P0
defects along sensible commit boundaries; phase 3 runs the SPEC §10 smoke pass
end-to-end (and now also absorbs the DOM + event-log verification surface that
was originally Phase 2). An optional phase 4 absorbs the P1/P2 follow-ups (SW
state persistence, `fetch` interception fix, `meta.json` field hardening,
`generate-icons.js` ESM/CJS, dead-code cleanup).

**Roadmap re-phasing 2026-05-20:** original Phase 2 ("Stabilize DOM +
event-capture privacy") REMOVED per operator charter shift — archive flow is
internal-only (no external transmission); P0-5 password masking dropped as
v1 priority ("we don't care about privacy hardening. At least here."). DOM
+ event-log VERIFICATION (REQ-rrweb-dom-buffer + REQ-user-event-log) absorbed
by new Phase 3 (SPEC §10 smoke). REQ-password-confidentiality moved to Out
of Scope (v1). All subsequent phases renumbered: old 3 → new 2, old 4 → new
3, old 5 → new 4.

The journey: **broken-but-installable → playable video → working export →
green §10 smoke (incl. DOM + event-log verification) → 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.

- [x] **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-20** via gsd-verifier goal-backward audit GREEN (17/17 must-haves: 11 REQs/charters + 6 cross-cutting gates; see `.planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md`). Closure arc: 2026-05-15 (Plan 01-07) → 2026-05-16 (REOPENED on D-13 multi-EBML bug) → Plan 01-08 (WebM remux via ts-ebml + webm-muxer) → Plans 01-09/01-10 (whole-desktop + welcome-tab UX) → Plan 01-11 (spike-pivot) → Plan 01-12 (Design Integration) → Plan 01-13 (UAT harness 15/15 GREEN, 2026-05-19) → Plan 01-14 (monitorTypeSurfaces picker) → Plan 01-10 cycle-2 ack 'All good' 2026-05-20 + 5 inter-cycle debug fixes + brand-rename polish. 14/14 plans; 5 operator acks; 153/153 vitest + 24/24 UAT + Tier-1 grep 12 FORBIDDEN_HOOK_STRINGS all GREEN.
- [ ] **Phase 2: Stabilize export pipeline** — Close remaining export gaps (screenshot-at-click, meta.json schema, archive layout, manifest permission verification). Mostly already shipped via Plans 01-08 + 01-09 + 01-10 + 01-12 — narrowed scope post-re-phasing.
- [ ] **Phase 3: SPEC §10 smoke verification** — End-to-end install-and-record-and-export pass against all 9 acceptance criteria. **ABSORBS** REQ-rrweb-dom-buffer + REQ-user-event-log verification (originally Phase 2) per 2026-05-20 re-phasing. UAT harness extended with A24+ assertions for rrweb/event-log contracts.
- [ ] **Phase 4: Harden + clean up** _(optional)_ — P1/P2 follow-ups: SW state persistence, fetch interception, `meta.json` fields, `generate-icons.js` ESM/CJS, dead-code; plus deferred items from Phase 1 session (cursor visibility, dark-surface logo, tabs permission gap, 2 ffprobe flakes, ROADMAP backfill verification, rrweb 2.0.0-alpha.4 → stable v2 upgrade research).

## 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). All 14 functional plans complete; Phase 1 final-closure marker flip pending.
- [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). Closure-cycle follow-up debug `a2dfc8c` (startVideoCapture no-tab cleanup; D-01 dead-code removal) + `4bba679` (notifStartup text split into notifStartupCta + notifRecordingStarted) landed during Plan 01-10 closure 2026-05-20.
- [x] 01-10-PLAN.md — Welcome tab (Hero + Loom dial per D-02; first-install onboarding; chrome.runtime.onInstalled + chrome.storage.local flag-gating + chrome.tabs.create + canonical mokosh-mark.svg via Vite ?url import + canonical src/shared/tokens.css @import + chrome.i18n.getMessage for welcomeHeroRu + welcomeHeroEn; harness A15-A17 with A17.7 --mks-rec probe + A17.8 mark-bundling invariant; D-16-toolbar charter preserved). Closure 2026-05-20 via cycle-2 operator ack "All good" + 5 inter-cycle debug fixes (89e1e09 → 49f087f → 8f329d8 → b112cb7 → 4bba679 → d48a715 → 0854baf → a2dfc8c → d21ed17) + brand-rename follow-up "AI Call Recorder" → "Mokosh"; 153/153 vitest + 24/24 UAT GREEN.
- [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 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
(post-2026-05-20 amendment: 8-field shape with `urls: string[]` replacing
`url: string` + new `schemaVersion: '2'` cutover marker per D-P2-02 + D-P2-03),
and downloaded via an offscreen-minted Blob URL (closes audit P0-6 base64
data-URL cap; D-P2-01).

**Depends on**: Phase 1 (export consumes the video buffer + the rrweb/event-log
infrastructure already shipped in src/content/index.ts; original "Phase 2 DOM"
dependency removed per 2026-05-20 re-phasing).

**Scope note (2026-05-20):** Plans 01-08 (webm-remux + JSZip), 01-09 (popup
state machine + SAVE-only UI), 01-10 (welcome tab + i18n), and 01-12 (manifest
i18n + en+ru locales) already shipped most of the originally-planned export
surface. Phase 2 closes the AUDIT residuals: P0-6 (base64 → Blob URL via
offscreen-minted URL.createObjectURL per DEC-006) + P1 #10 (meta.json
`url:string` → `urls:string[]` schema migration) + strict 8-field schema
validation + UAT harness <5s latency assertion (REQ-archive-export-latency).

**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 (8 fields
     per the 2026-05-20 D-P2-02/D-P2-03 amendment: `schemaVersion`, `timestamp`,
     `urls`, `userAgent`, `extensionVersion`, `videoBufferSeconds`,
     `logDurationMinutes`, `totalEvents`; `urls` is a non-empty deduplicated
     `string[]` of operator tab URLs visited during the 30s window; 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/`.
  5. A real >2 MB archive downloads to disk successfully (the canonical 5-10 MB
     operator bug-report archive — previously failed via base64 data-URL cap).
     The chrome.downloads.download call site receives a `blob:` URL (not
     `data:application/zip;base64,`); URL.revokeObjectURL is dispatched via
     chrome.downloads.onChanged 'complete' (D-P2-01 lifecycle).

**Plans**: 4 plans (02-01 through 02-04). Wave 1 RED tests → Wave 2 parallel
implementation (Blob URL + meta.urls) → Wave 3 harness extension + operator
empirical checkpoint.
- [x] 02-01-PLAN.md — Wave 0 RED tests: blob-url-download.test.ts + meta-json-urls-schema.test.ts + strict-meta-json-validation.test.ts pinning D-P2-01/D-P2-02/D-P2-03 contracts (TDD)
- [x] 02-02-PLAN.md — Wave 1 Blob URL pipeline (D-P2-01, closes P0-6): offscreen CREATE/REVOKE handlers via base64-on-wire; SW downloadArchive rewrite; chrome.downloads.onChanged revoke lifecycle
- [x] 02-03-PLAN.md — Wave 1 meta.urls + tab-url-tracker (D-P2-02 + D-P2-03, closes P1 #10): SessionMetadata 7→8 fields with schemaVersion+urls; chrome.tabs.onActivated/onUpdated listeners; REQUIREMENTS.md REQ-meta-json-schema amendment
- [x] 02-04-PLAN.md — Wave 2 harness A24+A25+A26+A27 + operator empirical checkpoint: blob:URL prefix, <5s SAVE→zip latency, meta.json 8-field shape, multi-tab dedup; pre-checkpoint bundle gates + operator UAT cycle 1

**UI hint**: yes

### Phase 3: SPEC §10 smoke verification + DOM/event-log verification
**Goal**: All 9 SPEC §10 acceptance criteria pass against an unpacked load of
the build into a real Chrome instance. **ABSORBS** DOM + event-log verification
work (REQ-rrweb-dom-buffer + REQ-user-event-log) originally planned as Phase 2
per 2026-05-20 re-phasing.

**Depends on**: Phase 1, Phase 2.

**Requirements**: REQ-install-clean + REQ-rrweb-dom-buffer + REQ-user-event-log
+ 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 1–2 actually satisfies the SPEC.

**Absorbed Phase-2 scope (2026-05-20 re-phasing):**
- Verify rrweb 2.0.0-alpha.4 captures DOM events on typical pages (form +
  table + modal) without throwing in the Content Script console (REQ-rrweb-dom-buffer
  acceptance criterion #3).
- Verify the user-event log captures click + input (non-password) +
  navigation (popstate/hashchange) + js_error + network_error per
  CON-event-log-schema (REQ-user-event-log).
- Extend UAT harness with A24+ assertions covering the above contracts —
  consistent with Phase 1's Approach-B harness pattern (Plan 01-13).
- rrweb version research (foreground gsd-phase-researcher spawn) to verify
  alpha-pin is safe or if stable v2 has shipped. Deferred to Phase 4 if
  Phase 3 plans are tight.
- **NOT in scope:** P0-5 password masking (REQ-password-confidentiality
  dropped to Out of Scope v1 per "we don't care about privacy hardening.
  At least here." 2026-05-20).

**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. (Password
     masking criterion DROPPED per 2026-05-20 re-phasing —
     REQ-password-confidentiality is Out of Scope v1.)
  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 4: 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 3 (do not harden until §10 is green).

**Requirements**: none (no new v1 REQs; all v1 REQs are covered by phases 1–3)

**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 | 14/14 | **CLOSED 2026-05-20** via gsd-verifier audit GREEN (17/17 must-haves; commit 586836f); all markers flipped | 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; welcome-tab contract closed 2026-05-20 via Plan 01-10 cycle-2 operator ack "All good" + 5 inter-cycle debug fixes |
| 2. Stabilize export pipeline | 0/4 | Plans landed 2026-05-20 (4 plans: Wave 0 RED → Wave 1 Blob URL + meta.urls parallel → Wave 2 harness + operator checkpoint); execution pending | - |
| 3. SPEC §10 smoke + DOM/event-log verification | 0/TBD | Not started (absorbed Phase-2 DOM verification per 2026-05-20 re-phasing; ~2-3 plans) | - |
| 4. Harden + clean up (optional) | 0/TBD | Not started (deferred backlog: cursor visibility, dark-surface logo, tabs perm gap, ffprobe flakes, ROADMAP backfill, rrweb-version upgrade research, REQ-password-confidentiality v2 candidate) | - |
