Phase 1 closure 2026-05-15 — both acceptance gates green:
- D-12 ffprobe structural gate: `ffprobe -v error -f matroska -i
tests/fixtures/last_30sec.webm` exit 0 (cd61cbc)
- A3 empirical-playback gate: operator confirmed clean end-to-end Chrome
playback (no ~1 s freeze); ffmpeg `-v warning -i fixture -f null -`
exit 0 with zero decoder errors (only expected muxer DTS-monotonicity
warnings at segment join boundaries — documented D-13 trade-off for
multi-EBML-header WebM concat; Chrome's MSE pipeline handles this
natively, satisfying SPEC §10 #7)
Changes:
- .planning/REQUIREMENTS.md
* REQ-video-ring-buffer checkbox [ ] -> [x]; description AMENDED to
document the D-13 restart-segments lifecycle replacing D-09..D-11;
SPEC §10 #2, #3, #7 noted as green 2026-05-15
* Traceability table row: REQ-video-ring-buffer | Phase 1 | Complete
(was Pending)
- .planning/ROADMAP.md
* Phase 1 list-item flipped [ ] -> [x] with closure date + summary
* Phase 1 Success Criteria 1, 2, 3 individually checked off; criterion 2
re-worded to reflect D-13 segment-cycling (replacing the original
single-continuous-recorder wording from D-09..D-11)
* Plan list: 01-07-PLAN.md flipped [ ] -> [x] with closure note
* Progress table row: 7/7 Complete 2026-05-15 (was 6/7 In Progress)
* Phase 5 P1/P2 list: appended `getDisplayMedia` cursor visibility
constraint (`video: { cursor: 'always' }`) — surfaced as user
observation during Phase 1 smoke 2026-05-15
- .planning/STATE.md
* Frontmatter: status -> phase_complete, completed_phases 0 -> 1,
completed_plans 6 -> 7, percent 86 -> 100; stopped_at + last_activity
rewritten for closure narrative
* Current Position: Phase 1 COMPLETE, next Phase 2 of 5, Plan 7/7
complete, progress bar [██████████] 100% Phase 1
* Performance Metrics: Phase 1 row populated (7 plans, ~50 min); added
Plan 07 row with closure narrative incl. the two debug sessions
* Decisions log: appended [Phase 01-07-closure] decision and
[Phase 01-07-deferred-to-5] note for the cursor-visibility refinement
* Session Continuity: rewritten for closure; resume file points to the
Plan 07 SUMMARY (commit 3)
* Added "Phase 1 Closure Notes" block with ffprobe + ffmpeg gates,
fixture metadata, test counts, criteria green-status, and a process
retro candidate (auto-injection of empirical-acceptance gates when
RESEARCH.md flags HIGH-risk assumptions)
Refs: .planning/debug/resolved/d12-blob-port-transfer-fails.md,
.planning/debug/resolved/webm-playback-freeze.md
14 KiB
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 1–3 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.tabCapturewith offscreengetDisplayMedia(AMENDED from original DEC-003). Closed 2026-05-15 — D-12 ffprobe gate + A3 empirical-playback gate both green againsttests/fixtures/last_30sec.webm(1.6 MB VP9 1142×1038); D-13 restart-segments retired D-09..D-11 mid-phase; 30/30 vitest green, tsc clean. SPEC §10 #2, #3, #7 functionally satisfied (end-to-end Phase 4 smoke remains owner of §10). - Phase 2: Stabilize DOM + event capture privacy — Migrate rrweb to v2
maskInputFn, plugcontent/index.ts setupInputLoggingpassword leak - Phase 3: Stabilize export pipeline — Restore user-activation gesture in popup, delete dead
permissions.request, replace base64data: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.jsonfields,generate-icons.jsESM/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 invite.config.ts) into a single source of truth; fix themediaRecordershadow that breaksstopRecording. - 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.onUpdatedre-attachment; start ononInstalled/onStartup, not on popup open (CON-tab-capture-binding, REQ-video-ring-buffer).
Success Criteria (what must be TRUE):
- There is exactly one source of truth for the offscreen document; rebuilding
vite.config.tsdoes not regenerate a divergent inline duplicate, andstopRecordingruns withoutmediaRecorder is undefinedshadow errors. - With the extension loaded and an operator session active, a
MediaRecorderis 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 sameMediaStream) 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). - 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: 7 plans
- 01-01-PLAN.md — Doc cascade: amend DEC-003 / DEC-010 / RETIRE constraints / swap manifest permissions (D-A1..D-A6)
- 01-02-PLAN.md — Wave-0 test infrastructure: Vitest install + 4 RED test files + fixtures placeholder
- 01-03-PLAN.md — Offscreen recorder TDD: ring buffer + codec strict-mode + getDisplayMedia + track-ended cleanup; D-13 fallback skeleton pre-staged
- 01-04-PLAN.md — Port keepalive + OFFSCREEN_READY handshake (TDD): replaces alarms keepalive on offscreen side
- 01-05-PLAN.md — SW shrink: delete legacy buffer + alarms + IndexedDB + tabCapture paths; wire SW-side onConnect host
- 01-06-PLAN.md — Build pipeline collapse: delete vite.config.ts inline plugin + top-level offscreen/ dir; declare rollupOptions.input
- 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)
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 legacymaskInputSelectoris gone in v2.0.0-alpha.4 perpackage.json); fix the parallel leak insrc/content/index.ts setupInputLoggingso password field values are dropped at logger entry, not just at rrweb level.
Success Criteria (what must be TRUE):
- On a page containing
<input type="password">and elements withdata-sensitive="true", rrweb snapshots for that page mask the value of both kinds of fields (verified by inspecting exportedrrweb/session.json). - On the same page, typing into a password field produces no
inputevent entry containing the typed value in the user-event log (logs/events.json). - 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
getMediaStreamIdby moving the call to the popup-click handler; delete the deadpermissions.requestdance 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 lacksURL.createObjectURL(DEC-006, REQ-archive-export-latency).
Success Criteria (what must be TRUE):
- 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.downloadsdownload. - 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.jsonat the root) with no extra entries. meta.jsonvalidates against the verbatim CON-meta-json-schema (all 7 fields present, types correct,timestampis ISO-8601 withZ).manifest.jsonindist/afternpm run builddeclares exactly the permission set in DEC-011 with no additional or missing entries; loading unpacked into Chrome produces no permission-related warnings or errors inchrome://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 1–3 actually satisfies the SPEC.
Success Criteria (what must be TRUE):
- The extension installs into Chrome via "Load unpacked" against
dist/with no errors or warnings inchrome://extensions/. - 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).
- 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.
- A click on the popup button produces a ZIP in Downloads in under 5 s; the
ZIP opens;
video/last_30sec.webmplays in a browser. - 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 1–4)
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.
fetchinterception fix in the network-error path of REQ-user-event-log.meta.jsonfield hardening (timestamp source, version source, totalEvents derivation).generate-icons.jsESM/CJS compatibility with the rest of the toolchain.- Dead-code cleanup (the
permissions.requestdance removed in Phase 3 may have stranded helpers; the offscreen duality removed in Phase 1 may have stranded shims). getDisplayMediacursor 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 thegetDisplayMediaspec and Chrome implements it via theCursorCaptureConstraintenum (always/motion/never).
Success Criteria (what must be TRUE):
- 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).
- A page that issues a failing
fetch(response code >= 400) produces anetwork_errorentry inevents.json; a failingXMLHttpRequestdoes too. npm run buildandnode generate-icons.jsboth succeed under the project's module setting ("type": "module"inpackage.json) with norequire is not definedorCannot use import statement outside a moduleerrors.- 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 | 7/7 | Complete | 2026-05-15 |
| 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 | - |