Milestone v1 (v2.0.0): Mokosh — Session Capture #1

Merged
strategy155 merged 297 commits from gsd/phase-04-harden-clean-up-optional into main 2026-05-31 15:34:17 +00:00
3 changed files with 184 additions and 12 deletions
Showing only changes of commit f72bca5c46 - Show all commits

View File

@@ -263,7 +263,7 @@ finalized at plan time):
live references.
**Plans**: 7 plans (04-01 through 04-07). Wave 1 parallel (04-01 + 04-02) -> Wave 2 sequential (04-03 A29 rewrite -> 04-04 A33 SW persistence -> 04-05 A34 fetch+XHR) -> Wave 5 visual polish (04-06; operator empirical) -> Wave 6 closure (04-07).
- [ ] 04-01-PLAN.md — Audit P1 polish #11 + #14 + #15 (TDD; 3 unit tests + 3 src/content/index.ts edits)
- [x] 04-01-PLAN.md — Audit P1 polish #11 + #14 + #15 (TDD; 3 unit tests + 3 src/content/index.ts edits)
- [ ] 04-02-PLAN.md — Build/CSP hygiene (setimmediate polyfill replacement + dead-code grep + generate-icons.cjs rename)
- [ ] 04-03-PLAN.md — A29 cs-injection-world rewrite (strict-sentinel filter; closes ~1/3 flake)
- [ ] 04-04-PLAN.md — A33 SW state persistence (spike-first; 5-min idle + worker.close() CDP; ROADMAP SC #1)

View File

@@ -4,14 +4,14 @@ milestone: v2.0.0
milestone_name: milestone
status: executing
stopped_at: /gsd-resume-work — user selected Full Phase 4 (/gsd-execute-phase 4); HANDOFF.json consumed + deleted per workflow; ready for /clear then execute kickoff
last_updated: "2026-05-21T08:36:14.298Z"
last_activity: 2026-05-21 -- Phase 04 execution started
last_updated: "2026-05-21T12:31:09.937Z"
last_activity: 2026-05-21
progress:
total_phases: 4
completed_phases: 3
total_plans: 30
completed_plans: 23
percent: 77
completed_plans: 24
percent: 80
---
# Project State
@@ -28,12 +28,12 @@ no server, no password leaks.
## Current Position
Phase: 04 (harden-clean-up-optional) — EXECUTING
Phase 4 of 4 (Hardening — optional) — kickoff pending; milestone v1 may close here if hardening deferred
Plan: 1 of 7
Status: Executing Phase 04
Last activity: 2026-05-21 -- Phase 04 execution started
Phase 4 of 4 (Hardening — optional) — Plan 04-01 closed (audit P1 polish 3/3); 6 plans remain (04-02 build hygiene queued NEXT in Wave 1)
Plan: 2 of 7
Status: Executing Phase 04 — Plan 04-01 closed
Last activity: 2026-05-21 -- Plan 04-01 closed (audit P1 #11/#14/#15 polish; 9 new tests; vitest 171 -> 180/180 GREEN)
Progress: [███████████████░░░░░] Phase 1 + Phase 2 + Phase 3 complete · Overall 3/4 phases complete (Phase 4 optional)
Progress: [████████░░] 80%
### Plan 01-10 closure (2026-05-20)
@@ -147,6 +147,7 @@ Progress: [███████████████░░░░░] Phase 1
| 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 |
## Accumulated Context
@@ -194,6 +195,7 @@ current work:
- [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.
### Pending Todos
@@ -216,9 +218,9 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-05-21T10:25:00.000Z
Last session: 2026-05-21T12:29:05.588Z
Stopped at: /gsd-resume-work — user selected Full Phase 4 (/gsd-execute-phase 4); HANDOFF.json consumed + deleted per workflow; ready for /clear then execute kickoff
Resume file: .planning/phases/04-harden-clean-up-optional/04-01-PLAN.md (Wave 1 entry point; .continue-here.md preserved as secondary fallback)
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)

View File

@@ -0,0 +1,170 @@
---
phase: 04-harden-clean-up-optional
plan: 01
subsystem: content-script
tags: [audit-p1-polish, p1-11-fetch-url, p1-14-nav-url, p1-15-rrweb-timestamps, tdd, content-script, charter-d-p4-02]
# Dependency graph
requires:
- phase: 03-spec-10-smoke-verification-dom-event-log-verification
provides: REQ-user-event-log validated (A30 harness; 5 UserEvent types — click + input + navigation + js_error + network_error captured under canonical assertion shape)
- phase: 01-stabilize-video-pipeline
provides: tests/background/start-video-capture-no-tab.test.ts scaffold pattern (Plan 01-09 — pure-function-with-vi.fn-stub mirror for tests/content/)
provides:
- Audit P1 #11 fetch URL extraction fix — fetch(new Request(url)) now captures Request.url in events.json instead of literal '[object Request]'
- Audit P1 #14 navigation URL tracking fix — module-level `let previousUrl` tracker replaces stale `history.state?.url || 'unknown'` emit so meta.previousUrl carries the operator's prior URL verbatim
- Audit P1 #15 rrweb timestamp normalization — rrweb emit-side timestamps normalized to Date.now() Unix-epoch ms so cleanupOldEvents (now - event.timestamp) arithmetic is meaningful
- tests/content/ NEW directory mirroring src/content/ — establishes the content-script unit-test placement convention for Phase 4+
- 9 new vitest tests (171 baseline → 180 GREEN) pinning the three contracts above
affects: [04-02 (CSP/dead-code hardening; same Phase 4 wave), 04-04 (A34 fetch+XHR empirical harness will exercise the P1 #11 fix end-to-end), 04-07 (Phase 4 closure aggregator)]
# Tech tracking
tech-stack:
added: []
patterns:
- "Content-script unit-test scaffold: vi.mock('rrweb') + globalThis stubs for window/document/history/Request before await import('../../src/content/index'); userEvents+rrwebEvents read back via GET_RRWEB_EVENTS chrome.runtime.onMessage handler. Established at tests/content/ for Phase 4+ content-script work."
- "Type-narrow over implicit-coercion at trust boundaries: `args[0] instanceof Request ? args[0].url : String(args[0])` replaces `args[0]?.toString()` at the fetch wrapper sites. Discipline applied at both ok-branch (line ~190) AND catch-branch (line ~210) — twin sites carry the same narrow."
- "Module-level state for closure-time inter-event tracking: `let previousUrl` mutated swap-then-emit inside handleNavigation. Mirrors the existing rrwebEvents + userEvents module-level pattern at lines 21+24."
- "Wrapper-side timestamp normalization at emit time: `event.timestamp = Date.now()` prepended in rrweb record() emit callback. Same Unix-epoch convention as the existing addUserEvent normalization at line 54."
key-files:
created:
- "tests/content/fetch-interception.test.ts"
- "tests/content/navigation-tracking.test.ts"
- "tests/content/rrweb-timestamps.test.ts"
modified:
- "src/content/index.ts (3 surgical edits — module-level previousUrl declaration at line 29; handleNavigation rewrite at lines 106-122; fetch URL narrow at lines 190 + 210; rrweb emit timestamp normalization at line 315)"
- ".planning/config.json (use_worktrees: false — orchestrator-staged flip from earlier in this session; folded into Task 1 commit)"
key-decisions:
- "Task 1 (RED) added 4 fetch tests instead of the plan's nominal 3: Tests A+C are paired with new Tests B+D so both fetch interception sites (ok-branch line ~190 + catch-branch line ~210) get an explicit Request-arg test against the same canonical narrow. Plan acceptance allows ≥3 it() in fetch-interception.test.ts (min_lines: 60); 4 strengthens the regression net."
- "Used `(typeof window !== 'undefined')` guard in the module-level previousUrl initializer so the content script remains importable under Node-env vitest (vitest.config.ts environment: 'node'). Production content-script realm always has window; the guard is a one-line defensive bootstrap mirroring the project's import-time discipline (same guard pattern Plan 01-03 established for chrome.* in src/offscreen/recorder.ts)."
- "Did NOT introduce a helper function for the Request-or-string narrow. The inline form `(args[0] instanceof Request ? args[0].url : String(args[0])) || 'unknown'` reads clearly at the two call sites and avoids a new module-scope name. Optionally extractable in a future plan if a third call site appears."
patterns-established:
- "tests/content/ as the canonical content-script unit-test directory (parallel to existing tests/background/, tests/i18n/, tests/offscreen/, tests/build/)"
- "Reading content-script module state via the production GET_RRWEB_EVENTS chrome.runtime.onMessage path doubles as a contract pin for the archive export surface"
- "Audit P1 polish in src/content/index.ts is now closed end-to-end via a TDD pair (RED scaffolds + GREEN edits in cohesive sibling commits)"
requirements-completed: []
# Metrics
duration: ~30 min
completed: 2026-05-21
---
# Phase 4 Plan 01: audit-p1-polish-content-script Summary
**Three audit P1 correctness fixes in src/content/index.ts — fetch(new Request(url)) URL extraction at both fetch-wrapper sites, module-level previousUrl tracker replacing the always-"unknown" navigation emit, and Unix-epoch rrweb emit timestamp normalization — gated by 9 new RED→GREEN unit tests under tests/content/.**
## Performance
- **Duration:** ~30 min (RED scaffold + GREEN edits + bundle gates + SUMMARY)
- **Started:** 2026-05-21T11:55:00Z (after orchestrator respawn in foreground mode)
- **Completed:** 2026-05-21T12:25:00Z
- **Tasks:** 2 (Wave 0 RED + Wave 1 GREEN per plan's tdd type)
- **Files modified:** 4 (3 new test files + 1 source file edit + 1 config flip)
## Accomplishments
- Audit P1 #11 (fetch URL extraction) — `fetch(new Request(url))` now records the canonical `request.url` in events.json instead of the literal string `'[object Request]'`. Twin fix at both ok-branch (line ~190) and catch-branch (line ~210).
- Audit P1 #14 (navigation URL tracking) — module-level `let previousUrl` declared at line 29; handleNavigation rewritten to swap-then-emit so `meta.previousUrl` carries the operator's actual prior URL instead of the literal `'unknown'` produced by the stale `history.state?.url || 'unknown'` lookup.
- Audit P1 #15 (rrweb emit timestamps) — `event.timestamp = Date.now()` prepended in the rrweb `record({ emit })` callback at line 315; cleanupOldEvents `(now - event.timestamp) < RRWEB_RETENTION_MS` arithmetic at line 33 is now meaningful (both sides Unix-epoch ms).
- 9 new vitest tests across `tests/content/` pin all three contracts; baseline preserved (171 → 180/180 GREEN). tsc-clean preserved.
## Task Commits
Each task was committed atomically per the plan's TDD cycle:
1. **Task 1: Wave 0 RED — content-script test scaffolds**`3dbc51c` (test)
- 7 of 9 tests RED at commit (B+D fetch, A+B+C navigation, A+B rrweb); 2 tests pass-as-baseline (A+C fetch — String(string)===string identity).
2. **Task 2: Wave 1 GREEN — 3 surgical edits in src/content/index.ts**`7da30af` (feat)
- All 9 tests flip GREEN; full vitest baseline preserved at 180/180.
**Plan metadata commit:** to follow this SUMMARY landing.
## Files Created/Modified
- `tests/content/fetch-interception.test.ts` (NEW; 4 tests pinning P1 #11 — fetch(stringUrl) ok-branch + fetch(new Request(url)) ok-branch + fetch(stringUrl) catch-branch + fetch(new Request(url)) catch-branch)
- `tests/content/navigation-tracking.test.ts` (NEW; 3 tests pinning P1 #14 — popstate + hashchange + history.pushState wrap all read meta.previousUrl from module-level state)
- `tests/content/rrweb-timestamps.test.ts` (NEW; 2 tests pinning P1 #15 — Test A normalization to >1e12 + Test B cleanupOldEvents arithmetic regression)
- `src/content/index.ts` (3 surgical edits — see Decisions Made for line-level details)
- `.planning/config.json` (use_worktrees flipped to false earlier in this session by the orchestrator; folded into Task 1 commit per orchestrator instruction)
## Decisions Made
**Inline narrow over extracted helper for P1 #11:** Considered factoring out `function extractFetchUrl(arg: unknown): string` at module scope; chose to keep the narrow inline at both call sites for the same reason the project consistently inlines `event.timestamp = Date.now();` in the existing addUserEvent path — the discipline reads at the call site, the call sites are two-and-only-two, and a future plan can refactor if a third caller appears.
**typeof window guard for module-level previousUrl:** Production content-script realm always has `window`; the `(typeof window !== 'undefined')` guard at line 29 keeps the module importable under vitest's Node environment, mirroring the defensive bootstrap pattern Plan 01-03 established for chrome.* in src/offscreen/recorder.ts.
**Test count bumped 8 → 9:** Plan nominal was 3+3+2=8 tests. Added a 4th fetch test (catch-branch + new Request) so both fetch-wrapper sites have an explicit Request-arg pin. Plan acceptance permits ≥3 it() per file; this strengthens the regression net for the catch-branch fix at line ~210 which would otherwise have a single regression-guard test (Test C with a string arg).
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] vi.mock('rrweb') in fetch + navigation tests**
- **Found during:** Task 1 (first npm test run)
- **Issue:** Initial RED-test scaffold for fetch-interception + navigation-tracking did not stub rrweb. The content script's `import { record } from 'rrweb'` pulled rrweb's lib/*.js through vitest's CJS transform pipeline; rrweb 2.0.0-alpha.4 ships ESM-only, so the import crashed with "exports is not defined in ES module scope" before any test body ran.
- **Fix:** Added `vi.mock('rrweb', () => ({ record: (_opts) => () => {} }))` at the top of both fetch-interception.test.ts and navigation-tracking.test.ts. The rrweb-timestamps test already mocked rrweb (it needs the captured emit callback), so this completes the pattern across all 3 files.
- **Files modified:** tests/content/fetch-interception.test.ts, tests/content/navigation-tracking.test.ts
- **Verification:** All 9 content tests imported cleanly post-fix; 7 RED + 2 baseline-pass as expected for Task 1's RED gate.
- **Committed in:** `3dbc51c` (Task 1 commit; deviation absorbed into the same Wave 0 RED commit because the fix was in the test files themselves, pre-RED-gate).
**2. [Rule 1 - Bug] Removed `static prototype: unknown` from FakeXHR stubs**
- **Found during:** Task 1 (first npm test run — vite:oxc parse error)
- **Issue:** Initial scaffold included `static prototype: unknown = { open: () => {}, send: () => {} };` on the FakeXHR class stub. The TC39 class fields proposal reserves `prototype` as a non-writable class property; oxc (Vite's parser) correctly rejects it with `Classes may not have a static property named 'prototype'`.
- **Fix:** Removed the `static prototype` line from all 3 test files. The instance-level open/send/addEventListener methods on FakeXHR suffice — the content script only assigns to `XMLHttpRequest.prototype.open` and `XMLHttpRequest.prototype.send` (production prototype chain), which is satisfied by the auto-generated class prototype.
- **Files modified:** tests/content/fetch-interception.test.ts, tests/content/navigation-tracking.test.ts, tests/content/rrweb-timestamps.test.ts
- **Verification:** Files parsed clean post-fix.
- **Committed in:** `3dbc51c` (Task 1 commit).
---
**Total deviations:** 2 auto-fixed (1 blocking, 1 bug — both in the new test files, both resolved pre-commit during the Task 1 RED iteration).
**Impact on plan:** Zero scope creep; deviations were test-side mechanics that surfaced during the first RED-gate run and got rolled into the same Task 1 commit before the RED gate was claimed. Source-side edits (Task 2) landed exactly as RESEARCH §"Specifics" and 04-PATTERNS.md spelled out.
## Issues Encountered
**Pre-existing vitest flakes** — 3 unrelated tests intermittently fail across baseline runs (`tests/background/blob-url-download.test.ts` 5000ms timeout race, `tests/background/webm-remux.test.ts` ffprobe frame count, `tests/offscreen/webm-playback.test.ts` ffmpeg dry-run). These are documented Phase 4 deferred items per `03-VERIFICATION.md` "Forward-Looking Deferred Items" + 04-CONTEXT.md §"Flake stabilization" — owned by Plan 04-03 (A29 race rewrite + parallel-vitest race + 2 ffprobe flakes). Confirmed pre-existing via `git stash` baseline runs (171/171 only on lucky runs; 168/171 or 165/171 on flaky runs). Plan 04-01 introduces no new flakes — final clean baseline run was 180/180 GREEN.
## Pre-Checkpoint Bundle Gates
Per saved memory `feedback-pre-checkpoint-bundle-gates.md` (6/6 standard inventory):
1. **Tier-1 FORBIDDEN_HOOK_STRINGS**`tests/background/no-test-hooks-in-prod-bundle.test.ts` 13/13 GREEN; inventory unchanged at 12 strings (Plan 04-01 added no harness hooks; pure source-side polish).
2. **SW CSP-safety grep** — 1 `new Function("")` in SW chunk `dist/assets/index.ts-8LkXuqac.js`. Pre-existing `vite-plugin-node-polyfills` setimmediate polyfill; documented at `.planning/phases/01-stabilize-video-pipeline/deferred-items.md`; Plan 04-02 owns the polyfill replacement. NOT a Plan 04-01 regression.
3. **Node-globals grep**`Buffer.copy / .isView / .length / .push / .shift` in SW chunk — all from JSZip internals (lowercase-b `buffer` field names + JSZip stream pipeline). Pre-existing across all Phase 1+2+3 builds.
4. **DOM-globals grep**`document.createElement / .createTextNode / .documentElement / .F` + `window.Math / .console / .localStorage / .process` in SW chunk — pre-existing shimmed-DOM references inside JSZip's text encoder fallback paths.
5. **manifest.json** — present at `dist/manifest.json`; `name: "__MSG_extName__"` (chrome.i18n message resolution intact). Plan 04-01 did NOT touch `_locales/` so en↔ru parity is untouched.
6. **Build itself**`npm run build` exits 0 in 4.66s; all chunks ship.
## Self-Check
- **Files created:**
- tests/content/fetch-interception.test.ts → FOUND
- tests/content/navigation-tracking.test.ts → FOUND
- tests/content/rrweb-timestamps.test.ts → FOUND
- **Files modified:**
- src/content/index.ts → FOUND (diff shows 3 surgical edits)
- .planning/config.json → FOUND (use_worktrees: false)
- **Commits:**
- 3dbc51c → FOUND (test(04-01): Wave 0 RED)
- 7da30af → FOUND (feat(04-01): Wave 1 GREEN)
- **Verification commands all green:**
- npm test -- tests/content/ --run → 9 passed (9)
- npm test -- --run → 180 passed (180) [confirmed twice; once on flaky run with 1 pre-existing failure, once 180/180 clean]
- npx tsc --noEmit → exit 0
- All 7 acceptance grep checks pass per Task 2 commit body
## Self-Check: PASSED
## Next Phase Readiness
- Plan 04-02 (build hygiene: vite.config.ts polyfill replacement + generate-icons ESM/CJS + dead-code grep + deferred-items.md update) is **unblocked** and queued NEXT in Wave 1. It modifies `vite.config.ts` + `src/background/index.ts` + `generate-icons.{js,cjs}` + 2 build tests + the Phase 1 deferred-items.md; zero overlap with Plan 04-01's `src/content/index.ts` surface.
- Plan 04-04 (A34 fetch+XHR network_error empirical harness; ROADMAP SC #2) will validate Plan 04-01's P1 #11 fix end-to-end against a real probe page in Wave 3.
- Audit P1 polish backlog: **CLOSED 3/3** (items #11 + #14 + #15 all GREEN with regression coverage).
---
*Phase: 04-harden-clean-up-optional*
*Completed: 2026-05-21*