Files
mokosh/.planning/STATE.md
Mark 28ebc1fe4e docs(04-05): complete A34 fetch+XHR network_error empirical plan
- 04-05-SUMMARY.md: A34 assertion closes ROADMAP SC #2 (fetch + XHR
  network_error capture); Plan 04-01 P1 #11 Request-narrow fix
  validated end-to-end; skip-mode UAT 34->35/35 GREEN
- STATE.md: position advanced (6/8 plans); Plan 04-05 closure note;
  decision-log entry; A33 full-mode SAVE-ack flake logged as Blocker
  (routed to /gsd-debug — Plan 04-08 deliverable, out of scope here)
- ROADMAP.md: SC #2 STATUS CLOSED; 04-05 row [x]; Phase 4 progress 6/8
- All 4 ROADMAP success criteria now closed (SC #1 Plan 04-08, SC #2
  this plan, SC #3+#4 Plan 04-02)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 13:02:58 +02:00

270 lines
42 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.
---
gsd_state_version: 1.0
milestone: v2.0.0
milestone_name: milestone
status: executing
stopped_at: "Completed 04-05-PLAN.md (A34 fetch+XHR network_error empirical; ROADMAP SC #2 CLOSED; skip-mode UAT 34->35/35 GREEN; full-mode bailed at pre-existing Plan 04-08 A33 SAVE-ack flake — A34 SKIPPED-not-reached but verified by skip-mode)"
last_updated: "2026-05-22T10:43:22.622Z"
last_activity: 2026-05-22
progress:
total_phases: 4
completed_phases: 3
total_plans: 31
completed_plans: 28
percent: 90
---
# Project State
## Project Reference
See: .planning/PROJECT.md (updated 2026-05-15)
**Core value:** When an operator hits a bug, one click MUST produce a
self-contained archive that lets support reproduce what happened — in under 5 s,
no server, no password leaks.
**Current focus:** Phase 04 — harden-clean-up-optional
## Current Position
Phase: 04 (harden-clean-up-optional) — EXECUTING
Phase 4 of 4 (Hardening — optional) — Plans 04-01..04-05 + 04-08 closed (6/8); 2 plans remain: 04-06 (visual polish; operator empirical) + 04-07 (closure aggregator). ROADMAP SC #1 + SC #2 both CLOSED.
Plan: 7 of 7 (04-06 NEXT)
Status: Ready to execute
Last activity: 2026-05-22
Progress: [█████████░] 90%
### Plan 04-05 closure (2026-05-22)
- A34 fetch + XHR network_error empirical assertion landed; ROADMAP SC #2 CLOSED.
- 2 atomic commits: `a20372a` (Task 1 — assertA34 page-side: cs-injection-world fetch(404)+XHR(404) injection) → `0712c24` (Task 2 — driveA34 host-side + 3-site orchestrator wiring).
- SUMMARY: `.planning/phases/04-harden-clean-up-optional/04-05-SUMMARY.md`.
- **A34 pattern**: `chrome.tabs.create('https://example.com/')` probe tab + `chrome.scripting.executeScript({world:'ISOLATED'})` injects TWO failing-request triggers — `fetch('https://example.com/404-fetch-a34-<stamp>')` + `new XMLHttpRequest(); open('GET','/404-xhr-a34-<stamp>'); send()` — into the content-script realm so BOTH production wrappers in `src/content/index.ts setupNetworkLogging` (window.fetch + XMLHttpRequest.prototype) intercept them. `-<stamp>` (Date.now()) uniqueness guard per T-04-05-02. The injected fetch is `.catch(noop)`'d so the network rejection does not surface as a separate js_error UserEvent.
- **driveA34 host-side**: JSZip-parses `logs/events.json`; filter-pipeline form (no `continue`) selects `network_error` entries whose `target` contains `404-fetch-a34` / `404-xhr-a34`; asserts ≥1 of each + `meta.status === 404`. `readMetaStatus` helper narrows `UserEvent.meta.status` (typed `Record<string,unknown>`) to `number` without an unchecked `any` cast.
- **Plan 04-01 P1 #11 validated end-to-end**: A34.4 confirmed the fetch `network_error` entry's `target` field carries the real URL (`https://example.com/404-fetch-a34-1779444293161`), NOT the literal `[object Request]` that pre-fix implicit coercion produced. This is the end-to-end proof — through the production bundle + SAVE→archive layer — that the Plan 04-01 Request-narrow unit-test fix works in a real Chrome page context.
- **Skip-mode UAT (`SKIP_LONG_UAT=1`): 34 → 35/35 GREEN** with A34 running for real (~25s); all 6 A34 checks PASS (A34.1 SAVE ack + A34.0a events.json present + A34.2 fetch entry + A34.3 XHR entry + A34.4 fetch status===404 + A34.5 XHR status===404). Diagnostics: `network_error count=2`, `fetch-entry count=1`, `xhr-entry count=1`.
- **vitest baseline 184/184 GREEN preserved** (Plan 04-05 added no unit tests — harness-only).
- **Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12** in both gates (A34 rides production `window.fetch` + `XMLHttpRequest.prototype` + `chrome.scripting`/`tabs` — no new `__MOKOSH_UAT__`-gated symbol).
- **Pre-checkpoint bundle gates 6/6 PASS**: Tier-1 hook-string grep on dist/ (0 leaks); SW CSP-safety (`new Function`=0, `eval`=0); Node-globals + DOM-globals (`Buffer.`/`window.`/`document.` matches are third-party `typeof ...<"u"` guarded feature-detection; SW chunk byte-identical to baseline — Plan 04-05 touched zero `src/` files); manifest validation (mv3, 8 permissions); Tier-2 leak gate (`synthetic-display-source`=0).
- **Full-mode UAT (5-min A33 real) blocked at A33.1 — pre-existing Plan 04-08 flake, NOT Plan 04-05**: full-mode showed 33/35; `A33.1` (SAVE_ARCHIVE ack) returned `success=false` but `A33.2`/`A33.3` PASS (the 1.56 MB video buffer survived the `worker.close()` SW kill + event-driven wake). Suspected MV3 race — the original `sendMessage` channel bound to the killed SW instance closes before the restarted SW resolves the callback, despite the new instance completing the save + writing the archive. The orchestrator bails on first failure, so A34 was SKIPPED-not-reached in full-mode. A34 itself is unaffected and fully verified by the skip-mode 35/35. Per `feedback-gsd-ceremony-for-fixes.md`, the A33 flake is Plan 04-08's deliverable and out of scope for a Plan 04-05 hot-fix — logged as a Blocker and routed to /gsd-debug. Does NOT block ROADMAP SC #2 (closed via the skip-mode A34 verification).
### Plan 01-10 closure (2026-05-20)
- Welcome tab landed end-to-end across 4 waves (5 plan tasks: 4 autonomous + 1 operator empirical UAT cycle 2 + cycle-2 follow-up brand-rename ack)
- 4 plan-wave commits: `89e1e09` (Wave 0 RED onboarding tests) → `49f087f` (Wave 1 welcome bundle + Vite entries + manifest) → `8f329d8` (Wave 2 openWelcomeIfFirstInstall + onInstalled wiring) → `b112cb7` (Wave 3 harness A15+A16+A17)
- 5 inter-cycle debug commits between cycle-1 rejection (2026-05-20 ~08:56) and cycle-2 ack (2026-05-20):
1. `4bba679` fix(01-09): notifStartup text split — notifStartupCta for onStartup; notifRecordingStarted reserved for manual-start (debug 01-09-startup-notification-misleading-text)
2. `d48a715` fix(01-10): welcome page mark — bundle canonical mokosh-mark.svg via Vite ?url import + populateMark() + .welcome-hero__mark-img + A17.8 sub-check (debug 01-10-welcome-page-missing-mark; Plan 01-12 must_have #9 path-A swap-in gap closed)
3. `0854baf` fix(01-10): vitest build-test it() timeout — bump to 30s for slower welcome-page build (debug 01-10-vitest-build-test-timeout)
4. `a2dfc8c` fix(01-09): startVideoCapture — remove stale active-tab dependency (debug 01-09-notification-start-no-active-tab; D-01 cleanup gap; +3 RED→GREEN tests at start-video-capture-no-tab.test.ts)
5. `d21ed17` fix(01-12): brand polish — replace stale 'AI Call Recorder' refs with Mokosh (debug 01-12-stale-ai-call-recorder-references; 4 files: src/welcome/copy.ts + README.md + package.json + tests/i18n/manifest-i18n.test.ts)
- SUMMARY: `.planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md`
- **First-install activation pattern**: `chrome.runtime.onInstalled('install')` + `chrome.storage.local` flag-gating (`onboarding-completed: true` + `installed-at: Date.now()`) + `chrome.tabs.create` + fire-and-forget `.catch` defense-in-depth. Subsequent installs/updates do NOT re-open (A16's contract empirically verified).
- **Plan 01-12 path-B contract honored end-to-end**: `welcome.css` opens with `@import '../shared/tokens.css';` (canonical tokens — Lora display + IBM Plex Sans UI + D-04 Loom palette); NO placeholder welcome-tokens.css file. `chrome.i18n.getMessage` for `welcomeHeroRu` + `welcomeHeroEn` (Plan 01-12 fallback pattern preserved with `|| <en-const>`). Non-tagline copy via in-file COPY map (engineering placeholder pending future copy-iteration plan).
- **Canonical Mokosh mark bundled via Vite `?url` import**: `import markUrl from '../shared/brand/mokosh-mark.svg?url'`; Vite default-inlines ~600 B SVG as `data:image/svg+xml,...` in the welcome chunk; @crxjs/vite-plugin auto-WARs the welcome page transitively. `globals.d.ts` ambient module declaration added for `*.svg?url`.
- **A17.7 empirical probe**: `getComputedStyle` on transient div with `color: var(--mks-rec)` resolves to `rgb(178, 84, 61)` (= #b2543d = --mks-madder-600 per Plan 01-12 Wave 4 D-04 Loom palette adoption). Proves canonical @import wires through to canonical token values, not just engineering placeholders.
- **A17.8 sub-check added during welcome-mark debug**: verifies welcome chunk JS contains the inlined `data:image/svg+xml,...` data URL with canonical `viewBox='0 0 32 32'` preserved.
- **Pre-checkpoint bundle gates per saved memory `feedback-pre-checkpoint-bundle-gates.md`**: Tier-1 hook-string grep + SW CSP-safety + Node-globals + DOM-globals + manifest validation + en↔ru parity — all PASS. setimmediate polyfill `new Function` in SW chunk confirmed pre-existing (logged at `.planning/phases/01-stabilize-video-pipeline/deferred-items.md` for Phase 5 hardening per Plan 01-12 Wave 7 disclosure). Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 (A15-A17 use chrome.tabs.query + chrome.storage.local.get + fetch + DOMParser + getComputedStyle production APIs exclusively).
- **vitest 147 → 153 GREEN** (+3 from `tests/background/onboarding.test.ts` Wave 0; +3 from `tests/background/start-video-capture-no-tab.test.ts` closure-cycle debug `a2dfc8c`).
- **UAT harness 21/21 → 24/24 GREEN** (A0-A14 + A15-A17 + A18-A22 + A23 inclusive). A22 functionally GREEN now that welcome.html is reachable (was conditional skip-gate in Plan 01-12 Wave 6).
- **Operator empirical UAT cycle-2 ack 2026-05-20 verbatim "All good"** on welcome page + canonical mark + Lora-rendered hero + Cyrillic tagline + onStartup notification CTA + notification.onClicked → startVideoCapture path + reload-does-not-re-open + re-install branch.
- **Cycle-2 follow-up brand-rename ack** received same day for `d21ed17` (4-file content rename "AI Call Recorder" → "Mokosh"; `.planning/intel/*` audit trail preserved).
- **Plan 01-10 closed**: 5/5 plan tasks complete (4 autonomous Waves 0-3 + Wave 4 operator empirical cycle-2 ack); 4/4 plan waves complete.
- **Phase 1 implications**: final functional plan delivered. Phase 1 final-closure unblocked pending REQUIREMENTS / ROADMAP / STATE marker flip + optional `/gsd-verify-work 1`.
### Plan 01-12 closure (2026-05-20)
- Design integration landed end-to-end across 7 waves (10 plan tasks + 1 operator empirical checkpoint)
- 9 implementation commits: `3fe018b` (plan baseline revision post-01-14) → `34a9ce1` (Wave 0 RED scaffolds) → `f86fd60` + `abab6e1` (Wave 1 fonts + tokens.css) → `7732a30` (Wave 2 icons) → `110cebc` (Wave 3 manifest i18n + _locales) → `468f16d` (Wave 4 source adoption) → `e8d2881` (Wave 5 Vite define + welcome conditional) → `b909c37` (Wave 6 A18-A22) + 1 pre-checkpoint commit `865d394`
- SUMMARY: `f319c7d` (`.planning/phases/01-stabilize-video-pipeline/01-12-SUMMARY.md`)
- **R2 designer substitution**: Newsreader → Lora (Cyreal foundry; OFL-1.1; full Latin + Cyrillic) per designer reply 2026-05-19. Canonical token value `--mks-font-display: "Lora", "Iowan Old Style", "Times New Roman", serif`.
- **MV3 CSP self-host invariant verified**: zero `googleapis` / `https://fonts` references in `dist/` (per `tests/build/no-remote-fonts.test.ts`); 8 local @font-face rules in `src/shared/tokens.css`; ~155 KB font bundle ships under `src/shared/fonts/` with LICENSE + README attribution.
- **16 i18n keys across `_locales/{en,ru}/messages.json`**: extName + extDesc + tooltipOff + tooltipRecPrefix + tooltipErr + popupSavePrompt + popupSaveCta + popupSaveDone + popupSaving + popupSaveDoneShort + popupEmptyState + popupInfoText + notifStartup + notifRecovery + welcomeHeroRu + welcomeHeroEn. EN extName = "Mokosh — Session Capture" (D-07); EN extDesc = "Thirty seconds ago, always at hand." (D-08); RU extName = "Mokosh — Запись сессии"; RU extDesc = "Тридцать секунд назад, всегда под рукой." en↔ru parity verified by `tests/i18n/locale-parity.test.ts`.
- **Branded Loom-mark icons** (D-01): 8-bit RGBA via `rsvg-convert`; replaces Bug A placeholders (was 16-bit RGB at 574/1153/2615 B; now 406/784/1952 B 8-bit RGBA). Clears Chrome imageUtil silent-rejection floors (16≥200B, 48≥500B, 128≥1024B).
- **BADGE_REC_COLOR** flipped from material-green `#00C853` to madder `#b2543d` (= --mks-madder-600 per D-04 loom palette).
- **src/popup/style.css** carries ZERO hex literals (every color via `var(--mks-*)`); imports `../shared/tokens.css`. src/popup/index.ts + src/background/index.ts read `chrome.i18n.getMessage('<key>') || '<en-const-fallback>'` at every operator-facing site.
- **UAT harness A18-A22** following Plan 01-13 Approach B pattern (page-side `assertA*` + host-side `driveA*` + harness orchestrator). FORBIDDEN_HOOK_STRINGS at 13 entries (+1 over 01-14's 12 baseline for `data-mks-key` completeness). Full A1-A14 + A18-A22 + A23 chain runs in ~95s under Puppeteer headless.
- **Pre-checkpoint bundle gates** per feedback-pre-checkpoint-bundle-gates.md established: SW CSP grep (new Function/eval) + SW Node-globals grep (Buffer.*) + DOM-globals grep + manifest validation + en↔ru parity. Discovery: setimmediate polyfill `new Function` in SW chunk via `vite-plugin-node-polyfills` — VERIFIED pre-existing across Phase 1 history; NOT a Plan 01-12 regression; logged at `.planning/phases/01-stabilize-video-pipeline/deferred-items.md` for Phase 5 hardening.
- **vitest: 100 → 147 GREEN** (+47 across 6 new test files at tests/build/ + tests/i18n/).
- **UAT harness: 16/16 → 21/21 GREEN** (A18-A22 added; A22 skip-gates on Plan 01-10 absent).
- **Operator empirical brand-fit ack 2026-05-20 verbatim "all good"** on fresh build + load unpacked + branded-surface verification (toolbar Loom icon, popup loom palette + Lora display heading, manifest:name resolution to "Mokosh — Session Capture", Russian copy rendering with Lora, notification copy via chrome.i18n).
- **Plan 01-13 Task 9 (operator brand/design ack on loaded extension) functionally CLOSED** via this checkpoint — same operator + same empirical surface coverage; the LAST remaining Phase 1 brand-design gate.
### Outstanding Phase 1 gates
- ~~**Plan 01-10 (welcome tab):**~~ CLOSED 2026-05-20 via cycle-2 operator ack "All good" + 5 inter-cycle debug fixes + brand-rename follow-up (SUMMARY at `.planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md`; 153/153 vitest + 24/24 UAT GREEN; A22 flipped from skip-gate to functionally GREEN)
- **Phase 1 final-closure marker flip:** pending REQUIREMENTS / ROADMAP / STATE markers + optional `/gsd-verify-work 1`
### Plan 01-13 closure (2026-05-19; brand/design ack subsequently closed via Plan 01-12 Wave 7 2026-05-20)
- Puppeteer-based UAT harness: `npm run test:uat` exits 0 with **15/15 GREEN** (A0-A14)
- Bug A regression rewind empirically verified (commit body 6a77967)
- Bug B regression rewind empirically verified (commit body b665919)
- Plan 01-09 functional contract closed via harness PASS per `01-09-PLAN.md` Amendment 2
- Operator UAT Task 9 ack'd 2026-05-19 ("all good" — recovery + restart-after-click covered by harness A7 + A2)
- **Save-stops-recording charter divergence fixed inline via debug session** (`.planning/debug/resolved/01-09-save-stops-recording.md`):
- Symptom: SAVE created zip but did NOT stop recording (badge stayed REC; Chrome share banner persisted)
- Root cause: implementation 01-09 over-extended "always-on safety net" framing; SPEC intent is one-shot
- Fix: SW SAVE_ARCHIVE handler dispatches STOP_RECORDING + setIdleMode in finally (4f4c3e2)
- Harness regression coverage: A14 added (2b6c24b) — post-SAVE state check (badge='', popup='', no new recovery notif)
- **CHARTER REVERSAL 2026-05-19 — save-does-not-stop-recording** (`.planning/debug/resolved/01-09-save-does-not-stop-recording.md`):
- Operator UX iteration: prefers original "always-on safety net" framing (continuous recording; SAVE only creates a new zip)
- Revert: SW SAVE_ARCHIVE `finally` block REMOVED (commit 7645765)
- Test file inversions: `tests/background/save-archive-does-not-stop-recording.test.ts` (renamed via `git mv`, history preserved; commit 6ac23fd)
- Harness A14 inverted to assert continuous-recording post-SAVE: badge='REC', popup endsWith popup.html, no new recovery notif (commit 1baaf45)
- Plan 01-09 Amendment 3 landed documenting the reversed charter
- vitest preserved at 98 GREEN; `npm run test:uat` preserved at 15/15 GREEN under inverted contract
- Plan 01-11 closed as spike-pivot (ba5474c SUMMARY); architecture lessons (no `await import(...)` in SW; `track.dispatchEvent('ended')` not `track.stop()`; `__MOKOSH_UAT__` Vite define-token) carried forward into Plan 01-13's Approach B harness
- vitest: 83 → 98 GREEN across Plan 01-13 (+15: Tier-1 grep gate strings + hook contract tests + save-stops unit tests)
### Outstanding Phase 1 gates
- ~~**Plan 01-13 Task 9 (operator checkpoint):**~~ CLOSED 2026-05-20 via Plan 01-12 Wave 7 brand-fit ack "all good"
- ~~**Plan 01-12 (design integration):**~~ CLOSED 2026-05-20 (R2 Lora substitution + tokens.css canonical + 16 i18n keys + branded icons + manifest i18n; SUMMARY f319c7d)
- ~~**Plan 01-10 (welcome tab):**~~ CLOSED 2026-05-20 via cycle-2 operator ack "All good" + 5 inter-cycle debug fixes + brand-rename follow-up; SUMMARY at `.planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md`
- **Phase 1 final-closure marker flip:** pending REQUIREMENTS / ROADMAP / STATE markers + optional `/gsd-verify-work 1`
## Performance Metrics
**Velocity:**
- Total plans completed: 9
- Average duration: —
- Total execution time: —
**By Phase:**
| Phase | Plans | Total | Avg/Plan |
|-------|-------|-------|----------|
| 1. Stabilize video pipeline | 7 | ~50 min (+ 2 debug sessions ~45 min) | 7 min |
| 2. Stabilize DOM + event capture privacy | 0 | — | — |
| 3. Stabilize export pipeline | 0 | — | — |
| 4. SPEC §10 smoke verification | 0 | — | — |
| 5. Harden + clean up | 0 | — | — |
| 02 | 4 | - | - |
| 03 | 5 | - | - |
**Recent Trend:**
- Last 5 plans: 4min, 4min, 8min, 3min, ~10min (Plan 07 closure incl. debug-session arbitration)
- Trend: stable execution time; complexity surfaced in debug sessions (pre-staged fallbacks activated cleanly)
*Updated after each plan completion*
| Phase 01 P01 | 4min | 6 tasks | 6 files |
| Phase 01 P02 | 4min | 5 tasks | 8 files |
| Phase 1 P03 | 8min | 3 tasks | 5 files |
| Phase 01 P04 | 4min | 3 tasks | 1 files |
| Phase 01 P05 | 8min | 2 tasks | 1 files |
| Phase 1 P06 | 3min | 2 tasks | 2 files |
| Phase 1 P07 | ~10min closure + 2 debug sessions (D-12 + A3) | 2 tasks (checkpoint + auto) | 6 files (fixture + REQUIREMENTS + ROADMAP + STATE + SUMMARY + plan-final-commit) |
| Phase 01 P14 | 49m | 1 tasks | 7 files |
| Phase 01 P12 | ~10h cumulative (7 waves; 10 plan tasks + 1 operator empirical checkpoint) | 10 tasks (7 waves + Wave 7 pre-checkpoint + brand-fit ack) | ~50+ files (8 WOFF2 + 3 PNG + 2 _locales + tokens.css + 6 unit-test files + harness + scripts + 4 source files modified) |
| Phase 01 P10 | ~5h cumulative (4 waves; 5 plan tasks + 5 inter-cycle debug sessions + cycle-2 follow-up brand-rename ack) | 5 tasks (Wave 0 RED + Wave 1 bundle + Wave 2 SW wiring + Wave 3 harness + Wave 4 operator UAT cycle-2) | 14 files (4 new src/welcome/* + globals.d.ts + 2 unit-test files + 3 harness files + src/background/index.ts + manifest + 2 Vite configs + closure-cycle debug touches: _locales + README + package.json + onstartup-notification.test.ts + onboarding-tests + manifest-i18n.test.ts) |
| Phase 04 P01 | 30m | 2 tasks | 5 files |
| Phase 04 P02 | 41min | 2 tasks | 5 files |
| Phase 04 P03 | 46min | 2 tasks | 2 files |
| Phase 04 P04 | ~25min | - tasks | - files |
| Phase 04 P05 | ~45min | 2 tasks | 3 files |
## Accumulated Context
### Decisions
Decisions are logged in PROJECT.md Key Decisions table (DEC-001 through
DEC-012, all SPEC-Accepted and locked for Phase 1). Recent decisions affecting
current work:
- Phase 1 framing: roadmap treats the existing codebase as a partially-broken
first attempt to be remediated against the SPEC, not as greenfield. The
7 P0 defects from the audit are split across phases 13 along commit
boundaries; phase 4 is end-to-end SPEC §10 smoke verification.
- All 12 SPEC decisions (`DEC-001`..`DEC-012`) are LOCKED for Phase 1.
Changing any of them requires a formal ADR; none are formally LOCKED in the
ingest classification, so a future ADR can revise.
- [Phase ?]: Doc cascade: amendments append (do not replace) original DEC/CON blocks to preserve SPEC provenance — Established convention for future SPEC-amending phases; downstream readers see both old + new with citation
- [Phase ?]: Manifest: drop alarms permission entirely rather than retain for re-use — Plan 05 deletes the alarms code path; declaring unused permissions expands attack surface (T-1-02)
- [Phase ?]: Pinned vitest at ^4 (4.1.6 latest stable; 5.x still beta on 2026-05-15)
- [Phase ?]: Phase 1 Wave-0 test infra: 4 RED tests committed against not-yet-existent src/offscreen/recorder.ts — pins contracts for Plans 03+04
- [Phase ?]: Reverted premature REQ-video-ring-buffer Complete marking left by Plan 01-01; satisfied by Plans 03+04+07, not by Wave-0 RED tests
- [Phase 01-03]: Bundled OffscreenLogger into Task 2 commit (Rule 3 blocking dependency — recorder.ts cannot typecheck without the import)
- [Phase 01-03]: Defensive bootstrap guard (typeof chrome check) lets pure ring-buffer test import recorder module without chrome stub
- [Phase 01-03]: Removed SW-side VIDEO_CHUNK/VIDEO_CHUNK_SAVED branches + IndexedDB helpers inline (tsc-clean requires; Plan 05 owns remaining SW shrink)
- [Phase 01-04]: Kept Plan 03's defensive bootstrap guard (typeof chrome / per-API existence checks) instead of Plan 04's verbatim unguarded block — Plan 04's verbatim block regressed ring-buffer and codec-check tests (they don't stub full chrome surface); restored guard preserves Plan 02 RED contract while satisfying Plan 04's new GREEN contract. Rule 1 deviation.
- [Phase 01-04]: T-1-04 SW-side sender check documented redundantly (4 places in recorder.ts) for Plan 05 executor visibility — Offscreen is trusting party; SW is validating party. Documenting in module header, port-name constant, threat-mitigation comment near bootstrap, and inline at connectPort makes the contract impossible to miss when grepping for T-1-04 during Plan 05.
- [Phase 01-04]: REFACTOR pass NOT skipped: stale 'Plan 04 wires this' comments replaced with actual D-17/Pattern 5 citations — Forward-pointing TODO-style comments became misleading after the work landed; minimal correctness-preserving comment update with all 9 tests still GREEN.
- [Phase ?]: [Phase 01-05]: Deleted broken checkPermissions / requestPermissions flow (Rule 1)
- [Phase ?]: [Phase 01-05]: REQUEST_PERMISSIONS collapsed — under getDisplayMedia (D-01) no runtime perm check is meaningful; the broken 'tabCapture' permission check was sending recording-start into the never-granted branch
- [Phase ?]: [Phase 01-05]: Added chrome.offscreen.hasDocument() in initialize() — Rule 2 robustness, audit P1 #8 mitigation across SW respawns
- [Phase ?]: [Phase 01-05]: SW is now a pure coordinator — onConnect host bound to 'video-keepalive' port with T-1-04 sender check; getVideoBufferFromOffscreen replaces synchronous SW-local buffer fetch; OFFSCREEN_READY handshake closes the audit P1 #12 race
- [Phase ?]: [Phase 01-05]: indexedDB.deleteDatabase('VideoRecorderDB') in onInstalled — T-1-NEW-05-02 / RESEARCH.md Runtime State Inventory cleanup of orphaned IDB from pre-Phase-01 builds
- [Phase ?]: [Phase 01-06]: Collapsed vite.config.ts from 226 -> 21 lines (RESEARCH.md Example B verbatim); deleted 174-line inline copy-offscreen plugin (audit P0 #1 root cause) and the orphan offscreen/ top-level directory (D-08)
- [Phase ?]: [Phase 01-06]: crxjs Outcome A confirmed — dist/src/offscreen/index.html (preserves src/ prefix from rollupOptions.input key). SW URL adjusted to chrome.runtime.getURL('src/offscreen/index.html'); RESEARCH.md Pitfall 5 binding empirically verified
- [Phase 01-07-debug-d12]: D-12 port-blob serialization fixed via base64 wire-format encode/decode (debug session d12-blob-port-transfer-fails resolved 2026-05-15). chrome.runtime.Port JSON-serializes payloads across extension contexts so Blob payloads were silently corrupted (JSON.stringify(blob) === "{}" → SW saw [{}, {}, ...] → new Blob([...]) coerced each to "[object Object]" → 75-byte text instead of WebM). Added src/shared/binary.ts (blobToBase64 / base64ToBlob), TransferredVideoChunk wire-format type, offscreen encode side, SW decode side. All 15 tests green incl. 6-test port-serialization spec. Re-run smoke.sh + ffprobe still required for end-to-end verification.
- [Phase 01-07-debug-a3]: D-13 restart-segments activated (debug session webm-playback-freeze resolved 2026-05-15). Plan 07 smoke retest after D-12 landed revealed the next-layer A3 failure: the ffprobe-valid WebM froze ~1 s into playback in Chrome because the single-continuous-recorder + 30 s age-trim lifecycle (D-09..D-11) evicted middle chunks containing VP9 keyframe references for retained tail chunks (orphan P-frames). Activated the pre-staged D-13 skeleton in src/offscreen/recorder.ts: stop+restart MediaRecorder every SEGMENT_DURATION_MS=10_000 ms on the same MediaStream, keep last MAX_SEGMENTS=3 self-contained WebM segments (3×10s=30s window preserved). Each segment fresh-encoded → own EBML header + seed keyframe → independently decodable. Side-effect: .stop() per segment fixes the "File ended prematurely" Matroska finalization gap. Type renames propagated: TransferredVideoChunk → TransferredVideoSegment, VideoChunk → VideoSegment, PortMessage.chunks → PortMessage.segments, VideoBufferResponse.chunks → VideoBufferResponse.segments; the header-pin flag from D-09..D-11 is dropped entirely. D-09..D-11 retired in favor of D-13. 28/30 tests pass; the 2 remaining reds are the empirical ffmpeg dry-runs against the still-stale committed fixture (operator regen required). REQ-video-ring-buffer NOT marked complete — Plan 07 still owns that, gated on the operator running ./smoke.sh then verifying Chrome playback + ffmpeg-clean stderr.
- [Phase 01-07-closure]: Phase 1 closed 2026-05-15: D-12 + A3 acceptance gates both passed. Operator-confirmed Chrome playback clean (no ~1 s freeze); ffmpeg `-v warning -i tests/fixtures/last_30sec.webm -f null -` exit 0 with zero decoder errors (only expected muxer DTS-monotonicity warnings at segment join boundaries — non-blocking, documented D-13 trade-off for multi-EBML-header concat); ffprobe + empirical playback both green; 30/30 vitest green (the 2 webm-playback empirical dry-runs flipped GREEN after the fresh fixture committed in cd61cbc); REQ-video-ring-buffer marked Complete; SPEC §10 #2, #3, #7 functionally satisfied (end-to-end Phase 4 smoke still owns the full §10 sweep). Three atomic closure commits land the fixture + REQ/STATE/ROADMAP flip + SUMMARY. Process note: Plan 01-07 surfaced TWO unanticipated-cascade failures (D-12 then A3); both had pre-staged fallbacks (base64 wire-format and D-13 restart-segments) that activated cleanly. Candidate retro: should `/gsd-plan-phase` auto-inject empirical-acceptance gates (ffmpeg dry-run + Chrome playback) before merging a phase when RESEARCH.md flags HIGH-risk assumptions?
- [Phase 01-07-deferred-to-5]: getDisplayMedia cursor visibility constraint (`video: { cursor: 'always' }`) surfaced as a user observation during Phase 1 smoke 2026-05-15. Captured frames lack the screen cursor despite it being the highest-signal cue for reproducing pointer-driven bugs. Constraint is opt-in per the getDisplayMedia spec; Chrome implements CursorCaptureConstraint (always/motion/never). Logged to Phase 5 P1/P2 hardening list — not blocking Phase 1 closure.
- [Phase ?]: Plan 01-14 — monitorTypeSurfaces:'include' shipped as top-level DisplayMediaStreamOptions constraint (W3C spec §6.1; Chrome ≥ 119 picker narrowing); A23 harness gate + Tier-1 grep lockstep extension to 12 strings; 100/100 vitest + 16/16 UAT GREEN. types.ts NOT modified — new cell/op are module-internal.
- [Phase 01-12]: Design integration landed end-to-end via 7 waves + operator brand-fit ack 2026-05-20 "all good". R2 designer substitution (Newsreader → Lora; Cyreal foundry; OFL-1.1; full Cyrillic via reply 2026-05-19) baked in; src/shared/tokens.css canonical with 8 local @font-face rules + zero remote URLs (MV3 CSP self-host invariant); 16 i18n keys per locale across en + ru with parity; branded Loom-mark icons replace Bug A placeholders (8-bit RGBA); src/popup + src/background migrated to chrome.i18n.getMessage with `|| <const>` fallback; BADGE_REC_COLOR flipped from material-green #00C853 to madder #b2543d (= --mks-madder-600 per D-04). UAT harness A18-A22 GREEN. Pre-checkpoint bundle gates established per feedback-pre-checkpoint-bundle-gates.md (5 grep gates pre-checkpoint; setimmediate polyfill new Function in SW chunk verified pre-existing across Phase 1 history — logged to deferred-items.md for Phase 5 hardening). vitest 100 → 147 GREEN (+47); UAT 16 → 21 GREEN (+A18-A22; A22 skip-gates on Plan 01-10 absent).
- [Phase 01-12]: Plan 01-13 Task 9 (operator brand/design ack on loaded extension) functionally closed via Plan 01-12 Wave 7 brand-fit ack 2026-05-20 (same operator + same empirical surface coverage). This was the LAST remaining Phase 1 brand-design gate.
- [Phase 01-10]: First-install operator-friendly activation landed end-to-end via 4 waves + Wave 4 operator empirical UAT cycle 2 ack "All good" 2026-05-20. chrome.runtime.onInstalled('install') + chrome.storage.local flag-gating + chrome.tabs.create with fire-and-forget .catch defense-in-depth (helper at src/background/index.ts:~186, called after initialize()). Plan 01-12 must_have #9 path-B contract honored end-to-end: welcome.css `@import '../shared/tokens.css';` resolves canonical tokens (Lora display + IBM Plex Sans UI + D-04 Loom palette); chrome.i18n.getMessage for welcomeHeroRu + welcomeHeroEn with `|| <en-const>` fallback (Plan 01-12 fallback pattern). Vite `?url` import + auto-WAR idiom bundles canonical mokosh-mark.svg as inline data URL in welcome chunk (debug 01-10-welcome-page-missing-mark resolved cycle-1 mark-bundling gap). Harness A15-A17 (24/24 UAT GREEN; A17 grew to 8 sub-checks incl. A17.7 getComputedStyle probe verifying --mks-rec resolves to rgb(178,84,61) AND A17.8 mark-bundling invariant). FORBIDDEN_HOOK_STRINGS unchanged at 12 (A15-A17 use chrome.tabs.query + chrome.storage.local.get + fetch + DOMParser + getComputedStyle production APIs exclusively). vitest 147 → 153 GREEN (+6: 3 onboarding tests + 3 start-video-capture-no-tab tests). 5 inter-cycle debug sessions resolved cycle-1 rejection + cycle-2 brand-rename ask: 4bba679 notifStartup text split + d48a715 welcome mark + 0854baf vitest timeout + a2dfc8c startVideoCapture no-tab cleanup + d21ed17 brand polish "AI Call Recorder" → "Mokosh".
- [Phase 01-10]: D-16-toolbar charter preserved verbatim — welcome page is informational + read-only; NO REQUEST_PERMISSIONS message type, NO chrome.runtime.sendMessage start path, NO duplicate getDisplayMedia trigger. CTA copy directs operator at toolbar icon. Toolbar onClicked (Plan 01-09) remains the SINGLE start path through chrome.action.onClicked in idle mode.
- [Phase 01-10]: Three-pipeline DOM population pattern established for src/welcome/welcome.ts: populateMark() walks [data-mokosh-slot='mark'] (canonical SVG via Vite ?url import); populateCopy() walks [data-mokosh-key] (textContent from in-file COPY map for non-tagline strings); populateI18n() walks [data-mokosh-i18n-key] (textContent from chrome.i18n.getMessage with `|| <en-const>` fallback for the D-08 tagline strings). Init order populateMark → populateCopy → populateI18n. Filter-pipeline form throughout (no continue per project style). data-mokosh-slot wrapper attribute preserved as design-swap landmark for forward-compat.
- [Phase 01-10]: Closure-cycle debug commit `a2dfc8c` removed pre-D-01 dead code from startVideoCapture (chrome.tabs.query({active:true}) + throw 'No active tab found') — the legacy block was load-bearing in the chrome.tabCapture era but functionally dead post-D-01 (getDisplayMedia whole-desktop in offscreen has no tab dependency). The bug surfaced via the notifications.onClicked path after the new CTA copy in 4bba679 explicitly invited the click. captureScreenshot() + saveArchive() retain their own genuine tab queries (out of scope for surgical fix). +3 RED→GREEN tests at start-video-capture-no-tab.test.ts pinning the new no-active-tab contract.
- [Phase ?]: [Phase 04-01]: Audit P1 polish landed end-to-end via TDD pair (3dbc51c RED + 7da30af GREEN). Three surgical edits in src/content/index.ts: (1) module-level let previousUrl tracker initialized at module load with typeof-window node-env guard, swapped-and-emitted in handleNavigation so meta.previousUrl carries the operator's actual prior URL (was always 'unknown'); (2) instanceof Request type-narrow inlined at both fetch-wrapper sites (ok-branch line ~190 + catch-branch line ~210), replacing args[0]?.toString() that resolved to literal '[object Request]' for fetch(new Request(url)); (3) event.timestamp = Date.now() prepended in rrweb record() emit callback at line 315, normalizing rrweb-internal page-load-relative timestamps to Unix-epoch ms so cleanupOldEvents (now - event.timestamp) arithmetic at line 33 is meaningful. 9 new vitest tests under tests/content/ (NEW directory) pin all three contracts; baseline 171 -> 180/180 GREEN; tsc-clean preserved; Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12. Audit P1 polish backlog CLOSED 3/3.
- [Phase ?]: [Phase 04-02]: Layered 4-mechanism CSP-hardening for transitive-polyfill pre-bundled-distribution interception (runtime queueMicrotask polyfill prelude + nodePolyfills exclude + resolve.alias.setimmediate + stripSetimmediateNewFunction Rollup post-transform plugin). Option α (force JSZip unbundled lib/index.js) attempted + reverted because it broke readable-stream-browser browser-field propagation causing UAT A30+ regressions. Option β preserves JSZip pre-bundled distribution verbatim while excising the offending literal post-bundle.
- [Phase ?]: [Phase 04-02]: ROADMAP SC #3 (generate-icons ESM/CJS) closed via git mv generate-icons.js generate-icons.cjs — Node 14+ treats .cjs as CJS regardless of package.json type:module per nodejs.org/api/packages.html#determining-module-system. No code change. ROADMAP SC #4 (dead-code grep permissions.request) GREEN regression-pinned via tests/build/dead-code-grep.test.ts. Plan 01-12 Wave 7 setimmediate deferred-items entry CLOSED end-to-end. SW chunk new Function count polarity flipped 1 → 0. UAT 33/33 GREEN preserved.
- [Phase 04-03]: A29 rewrite — cs-injection-world pattern (verbatim port of Plan 03-02 assertA30 / 03-03 assertA31 skeleton) + strict-sentinel filter (RESEARCH Q3 Code Example Pattern 3) closes the documented iana.org-leftover flake. assertA29 page-side: chrome.tabs.create(https://example.com) + chrome.scripting.executeScript world:'ISOLATED' injects sentinel-bearing <div> into document.body. driveA29 host-side: filter events by EventType.IncrementalSnapshot + IncrementalSource.Mutation, then descend into data.adds[*].node.textContent for 'a29-mutation-sentinel'. A29.2 strict-sentinel is THE primary check; A29.3 + A29.4 (Meta + FullSnapshot) preserved as defense-in-depth; pre-rewrite A29.5 (loose IncrementalSnapshot >=1) retired (subsumed). Empirical: 5/5 PASS across consecutive UAT runs (was ~2/3 historical). vitest 183/183 GREEN preserved. Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 (rides production chrome.tabs.create + chrome.scripting.executeScript per DEC-011 Amendment 1 grant + manifest scripting permission).
- [Phase ?]: [Phase 04-04]: Wave 0 SPIKE FAILED
- [Phase 04]: test
- [Phase 04-04]: Wave 0 SPIKE FAILED — empirically refutes RESEARCH Q2 MEDIUM-confidence A3 (offscreen-document independent lifecycle). videoSize=8505 bytes after 5min idle + Puppeteer CDP worker.close() (sanity floor 100KB; typical 1-3MB). 8505 bytes are corrupt WebM per ffprobe (End of file + Duplicate element; no valid clusters); rrweb/session.json=[]; logs/events.json=[]; meta.urls=chrome-extension://* only. Conclusion: src/offscreen/recorder.ts:91 'let segments: Blob[] = []' RAM-only architecture does NOT survive 5-min SW idle. ROADMAP SC #1 remains OPEN; Task 2 (A33 verification-only) BLOCKED by gating condition; plan-fix ceremony required to add IndexedDB persistence per RESEARCH Q2 sub-question b Option C. Spike-first contract honored — STOP at Task 1; do NOT improvise inline; route to plan-fix ceremony per saved-memory feedback-gsd-ceremony-for-fixes.md.
- [Phase 04-04]: stopServiceWorker(browser, extensionId) helper landed at tests/uat/lib/harness-page-driver.ts (verbatim Chrome devrel canonical pattern — Puppeteer >=22.1.0 worker.close()). Persisting artifact retained even though Task 2 BLOCKED — helper is non-empty positive scaffolding for the eventual IndexedDB-persistence plan-fix verification harness (A33-equivalent reuse). Pattern: spike-FAILED forensic-evidence — commit the spike script (tests/uat/spike-a33-sw-persistence.ts; 202 lines) AND the persisting helpers (not delete) so future plan-fix can re-run the exact reproducible test that revealed the failure.
- [Phase ?]: [Phase 04-08]: Methodology reframe — video-file MediaStream replaces canvas.captureStream throttling per debug session-2 verdict; A33 lands; UAT 33->34; ROADMAP SC #1 CLOSED 2026-05-22 (videoSize=1.8MB vs 8505 baseline); architecture UNCHANGED.
- [Phase ?]: [Phase 04-05]: A34 fetch+XHR network_error empirical lands (ROADMAP SC #2 CLOSED). cs-injection-world fetch(404)+XMLHttpRequest(404) from an example.com probe tab via chrome.scripting.executeScript ISOLATED; driveA34 host-side JSZip-parses logs/events.json + asserts 2 network_error entries (fetch+XHR) with meta.status===404. Plan 04-01 P1 #11 Request-narrow fix validated end-to-end — fetch entry target carries real URL not [object Request]. Skip-mode UAT 34->35/35 GREEN (A34 real, all 6 checks PASS). Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12; bundle gates 6/6 PASS; vitest 184/184 preserved. Full-mode 35/35 gate observed a pre-existing Plan 04-08 A33 SAVE-ack flake (A33.1 false; 1.56MB video buffer survived) — A34 SKIPPED-not-reached in that run, unaffected; A33 flake routed to /gsd-debug.
### Pending Todos
None yet.
### Blockers/Concerns
- (informational) `chrome.tabCapture` requires a user gesture on first
activation — Phase 3 (P0-4) restores this by moving the call into the popup
click handler; until Phase 3 lands, recording cannot start cleanly even if
Phase 1's pipeline is correct. Phases 13 should not be re-ordered.
- Plan 04-08 A33 full-mode SAVE-ack flake: in full-mode UAT (5-min idle real), A33.1 (SAVE_ARCHIVE ack) returned success=false even though A33.2/A33.3 PASS (1.56MB video buffer survived the SW worker.close() + event-driven wake). Suspected MV3 race: the original sendMessage channel bound to the killed SW instance closes before the restarted SW resolves the callback, despite the new instance completing the save + writing the archive. Orchestrator bails on first failure so A34 was SKIPPED-not-reached in full-mode. A34 itself is fully verified by skip-mode 35/35 GREEN. Route to /gsd-debug for A33 ack-channel hardening (out of scope for Plan 04-05 per feedback-gsd-ceremony-for-fixes.md — A33 is Plan 04-08's deliverable).
## Deferred Items
Items acknowledged and carried forward from previous milestone close:
| Category | Item | Status | Deferred At |
|----------|------|--------|-------------|
| *(none)* | | | |
## Session Continuity
Last session: 2026-05-22T10:43:10.855Z
Stopped at: Completed 04-05-PLAN.md (A34 fetch+XHR network_error empirical; ROADMAP SC #2 CLOSED; skip-mode UAT 34->35/35 GREEN; full-mode bailed at pre-existing Plan 04-08 A33 SAVE-ack flake — A34 SKIPPED-not-reached but verified by skip-mode)
Resume file: None
Prior session: 2026-05-21T08:22:59.958Z — /gsd-pause-work saved Phase 4 execution-ready handoff (dbcf482); Phase 4 plans validated iter-2 PASSED + 3 cosmetic advisories fixed
Earlier session: 2026-05-20T12:54:42.000Z — /gsd-pause-work saved Phase 2 execution-ready handoff (a440c7d); Phase 1 closed end-to-end via verifier audit GREEN (586836f); alpha distribution shipped (dist-archives/mokosh-build-2026-05-20-6dbed91.zip)
Earlier session: 2026-05-20T12:00:00.000Z — Plan 01-10 closed via cycle-2 operator ack "All good" + 5 inter-cycle debug fixes + brand-rename follow-up
Even earlier: 2026-05-20T08:00:00.000Z — Plan 01-12 closed via Wave 7 operator brand-fit ack 2026-05-20 'all good' (SUMMARY f319c7d; 147/147 vitest + 21/21 UAT GREEN)
Earlier session: 2026-05-19T19:41:05.737Z — Completed Plan 01-14 (commit b467123 + SUMMARY 5254145; 16/16 UAT + 100/100 vitest GREEN)
Even earlier session: 2026-05-17T14:30:13Z — resumed from /gsd-pause-work checkpoint ed82fd6; Bug A icons (a881bf0) + intel-unlock (f768498) committed; /gsd-debug spawned for Bug B state-machine routing (subsequently resolved via the recovery-flow amendment at Plan 01-09 Task 5 step 11)
## Phase 1 Closure Notes
- **ffprobe exit code:** 0 (`ffprobe -v error -f matroska -i tests/fixtures/last_30sec.webm`)
- **ffmpeg dry-run exit code:** 0 (`ffmpeg -v warning -i tests/fixtures/last_30sec.webm -f null -`) — stderr contains only the expected muxer DTS-monotonicity warnings at segment join boundaries; no decoder errors. Documented D-13 trade-off for multi-EBML-header WebM concatenation; Chrome's MSE pipeline handles this natively (SPEC §10 #7 scope: "plays back in a browser" — Chrome confirmed).
- **Fixture:** `tests/fixtures/last_30sec.webm` = 1 633 459 bytes (1.6 MB), VP9 codec, Profile 0, 1142×1038, color space bt709, time_base 1/1000, start_pts 0. Captured against the D-13 restart-segments recorder (3 × ~10 s self-contained segments).
- **Test suite:** 30/30 green across 8 files (`tests/offscreen/`); both empirical ffmpeg dry-runs in `webm-playback.test.ts` flipped GREEN after the fresh fixture committed in cd61cbc.
- **Phase 1 outcome:** SPEC §10 acceptance criteria #2 (continuous capture), #3 (≤ 30 s window), and #7 (last_30sec.webm plays in a browser) are functionally green at the Phase 1 level. End-to-end §10 smoke verification remains owned by Phase 4 (all 9 criteria sweep).
- **Phase 2 onwards:** Phase 2 owns the DOM/event-capture privacy slice (REQ-rrweb-dom-buffer, REQ-user-event-log, REQ-password-confidentiality). Phase 3 owns the popup state machine + base64-URL replacement. Phase 4 runs the full SPEC §10 smoke pass. Phase 5 absorbs P1/P2 hardening (now includes the `getDisplayMedia` cursor visibility refinement surfaced 2026-05-15).
- **Process retro candidate:** Plan 07 surfaced two cascade failures (D-12 binary transfer + A3 cluster alignment). Both had pre-staged fallbacks (base64 wire-format and D-13 restart-segments) which activated cleanly. The smoke-test step ended up doing the empirical-acceptance-gate work that RESEARCH.md flagged as HIGH-risk. Worth raising in a GSD-framework retro: should `/gsd-plan-phase` auto-inject empirical-acceptance gates (ffmpeg dry-run + Chrome playback) BEFORE merging a phase when RESEARCH.md flags HIGH-risk assumptions, rather than discovering it via Plan 07's smoke step?