# Phase 4: Harden + clean up (optional) - Context

**Gathered:** 2026-05-20
**Status:** Ready for planning

<domain>
## Phase Boundary

Final v1 milestone phase: eliminate accumulated P1/P2 follow-ups, audit residuals, flake instability, and operator-perceptible polish so the v1 release is not just spec-conformant but maintainable + operator-friendly. Per ROADMAP, this phase has NO new v1 requirements — all functional REQs are covered by Phases 1-3.

**Charter:** User explicitly chose "Full Phase 4" (most rigorous option). All 4 ROADMAP success criteria + meaningful subset of 12 deferred items from 03-VERIFICATION.md are IN scope.

**In scope:**

ROADMAP success criteria (4 originally defined):
1. SW state persistence across 30s idle unload/reload (5+ min idle export test produces non-empty video buffer)
2. fetch + XHR network_error capture in events.json (response code >= 400 verification)
3. generate-icons.js ESM/CJS compatibility (`npm run build` + `node generate-icons.js` both succeed under `"type": "module"`)
4. Dead-code grep returns no live references for permissions.request + duplicate offscreen inline string

Audit P1 polish (all three — D-P4-02):
5. P1 #11: fetch interception URL extraction fix — `args[0]?.toString()` becomes `[object Request]` when args[0] is a Request object; replace with `args[0] instanceof Request ? args[0].url : String(args[0])` at src/content/index.ts:147
6. P1 #14: navigation URL tracking — replace `history.state?.url` (always "unknown" in apps that don't populate it) with module-level previousUrl tracking; src/content/index.ts:99-109
7. P1 #15: rrweb timestamps semantics — rrweb timestamps are page-load-relative not Unix epoch; normalize against record start time or use `Date.now()` at emit; src/content/index.ts:36-42

Flake stabilization:
8. A29 zip-mtime race fix via cs-injection-world re-target (Plan 03-02 introduced the pattern; ~1/3 runs flake)
9. Pre-existing parallel-vitest Tier-1-build-step race (~1/5 full-suite runs)
10. 2 pre-existing ffprobe/ffmpeg vitest flakes (pre-date Phase 3)

Visual / operator-perceptible polish (D-P4-03):
11. getDisplayMedia cursor visibility constraint `video: { cursor: 'always' }` — operator-perceptible UX win (Plan 01-07 obs 2026-05-15); enables pointer-driven bug reproduction
12. Dark-surface logo contrast — Plan 01-10 obs 2026-05-20

CSP / build hygiene:
13. setimmediate polyfill `new Function` in SW chunk via `vite-plugin-node-polyfills` — pre-existing CSP-question; replace with manual polyfill or strip dependency

Harness extension polish:
14. A31 extended: grep rrweb/session.json for sentinel absence (one-line ext.) — folds into existing driveA31

Docs hygiene (D-P4-05):
15. ROADMAP backfill for Plans 01-08..01-13 (5 plans inline-tracked but not row-added per Plan 01-13 plan-checker flag #4)

Closure:
16. 04-VERIFICATION.md aggregator + alpha re-distribution + milestone v1 close prep

**Out of scope (deferred to v2 / v1.1 follow-up milestone):**
- rrweb 2.0.0-alpha.4 → stable v2 upgrade research + implementation (D-P3-03 + D-P4-01 charter): alpha-pin stable across 13 plans + 33/33 UAT GREEN; risk vs reward favors v1.1+ deferral
- Programmatic SW-realm RAM measurement via chrome.devtools Memory API (D-P3-04 + D-P4-01): research-heavy; A32 best-effort scaffolding + chrome://memory-internals operator path + alpha distribution coverage already accepted for §10 #9
- REQ-password-confidentiality v2 candidate (full rrweb v2 maskInputFn + data-sensitive HTML attribute guards): out of scope per charter shift "we don't care about privacy hardening" 2026-05-20; only revisit if charter reverses

</domain>

<decisions>
## Implementation Decisions

### Phase 4 scope (D-P4-01 — full Phase 4)

- **D-P4-01:** Full Phase 4 scope — all 4 ROADMAP success criteria + meaningful subset of 12 deferred items from 03-VERIFICATION.md. Exclusions per user choice: rrweb v2 upgrade + programmatic SW-RAM measurement. Estimated plan count: 6-8 plans across 2-3 waves.
  - **Rationale:** User picked "Full Phase 4 (Recommended for v1 quality)" 2026-05-20. Honors no-unilateral-scope-reduction memory + canonical ceremony preference. Phase 4 is the last chance to harden before v1 release; the audit P1/P2 items are real correctness issues (P1 #11 fetch URL is wrong; P1 #14 nav URL always "unknown"; P1 #15 rrweb timestamps wrong unit). Visual polish (cursor + dark-logo) is operator-perceptible quality.

### Audit P1 polish (D-P4-02 — all three)

- **D-P4-02:** Address all three audit P1 correctness items (#11 + #14 + #15) in a single dedicated plan or two cohesive plans.
  - **Rationale:** All three are correctness fixes in src/content/index.ts; surgical scopes per item; high ratio of fix-value to fix-cost. User chose "All three (Recommended)" 2026-05-20.

### Visual / operator-perceptible polish (D-P4-03 — both items)

- **D-P4-03:** Include both cursor visibility + dark-surface logo contrast.
  - **Rationale:** Cursor visibility is functional (pointer-driven bug reproduction is the highest-signal cue when operator reproduces a click-based bug; current captures lack the cursor entirely per Plan 01-07 obs 2026-05-15). Dark-logo is brand polish (Plan 01-10 obs 2026-05-20). Both are small surgical fixes that improve operator-facing quality before alpha re-distribution. User chose "Both (Recommended)" 2026-05-20.

### Alpha tester feedback integration (D-P4-04 — user-handled out-of-band)

- **D-P4-04:** Phase 4 execution proceeds independently of alpha tester feedback. User explicitly: "no no, if something i'll tell you" 2026-05-20 — alpha tester findings, if any, are user-routed via separate channels. Phase 4 does NOT wait for alpha confirmation. After Phase 4 closes, fresh build is re-packaged for testers as the v1-final candidate.
  - **Rationale:** Faster to v1 close. Honors user's operating mode (they curate operator-side signal themselves). Alpha distribution becomes a post-v1 quality validator + maintenance-window input, not a v1 gate.

### Docs hygiene (D-P4-05 — ROADMAP backfill in)

- **D-P4-05:** Include ROADMAP backfill for Plans 01-08..01-13 (5 plans inline-tracked but not row-added per Plan 01-13 plan-checker flag #4). Small surgical docs polish; ~15 min.
  - **Rationale:** ROADMAP rows are the canonical reference document; keeping it accurate is hygiene. User chose "Include" 2026-05-20.

### Claude's Discretion

- **Plan organization:** Planner picks 6-8 plan split. Suggested grouping:
  - 04-01: Bug / flake stabilization wave (A29 race + parallel-vitest race + 2 ffprobe flakes — 4 atomic tasks)
  - 04-02: Audit P1 polish (#11 fetch + #14 nav URL + #15 rrweb timestamps — cohesive content-script polish)
  - 04-03: SW state persistence (5min idle test — ROADMAP SC #1; harness extension)
  - 04-04: fetch + XHR network_error harness extension (ROADMAP SC #2; extends driveA30 or new driveA33)
  - 04-05: generate-icons ESM/CJS + dead-code grep + setimmediate polyfill (ROADMAP SC #3+#4 + CSP hygiene — build-side hardening)
  - 04-06: Visual polish — cursor visibility constraint + dark-logo contrast (operator-perceptible)
  - 04-07: A31 extension (one-line rrweb session.json sentinel grep) — could fold into 04-02
  - 04-08: VERIFICATION.md aggregator + ROADMAP backfill + alpha re-distribution + milestone v1 close prep
  Planner may consolidate (e.g., fold 04-07 into 04-02; merge 04-05 + 04-06 if cohesive) for 6-7 plans target.
- **Wave structure:** Likely 3-4 waves; sequential where files_modified overlap (Phase 2 + Phase 3 lessons cited).
- **Harness assertion numbering:** A33+ for new Phase 4 harness assertions (continues A29-A32 sequence from Phase 3).
- **Pre-checkpoint bundle gates:** Same 6/6 inventory per saved memory `feedback-pre-checkpoint-bundle-gates.md` (Tier-1 grep + SW CSP-safety + Node-globals + DOM-globals + manifest + en↔ru parity).
- **Tier-1 FORBIDDEN_HOOK_STRINGS:** stays at 12 unless A33+ needs new __MOKOSH_UAT__-gated symbols (audit at plan time; flag if increment needed).
- **CSP-safety mitigation for setimmediate polyfill:** options include (a) strip `vite-plugin-node-polyfills` and provide manual setimmediate=queueMicrotask polyfill, (b) configure plugin to use polyfills.builtins.setimmediate manually, (c) document acceptance with explicit CSP-allow rationale. Planner / researcher picks based on impact.
- **CSV / data file modifications:** none expected; Phase 4 is mostly src/* tweaks + tests/* additions + docs.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Phase scope + acceptance source

- `.planning/ROADMAP.md` §"Phase 4: Harden + clean up (optional)" — 4 ROADMAP success criteria verbatim + originally-framed P1/P2 list
- `.planning/REQUIREMENTS.md` (no new REQs in Phase 4; Phase 4 verifies tech-debt cleanup against already-shipped REQs)
- `.planning/PROJECT.md` — Validated section (all v1 REQs from Phase 1+2+3) + DEC-* table + Active section showing Phase 4 backlog
- `.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md` "Forward-Looking Deferred Items" table — 12 items inventory; Phase 4 cherry-picks ~10

### Audit residuals + bug-fix targets (read for P1 polish per D-P4-02)

- `.planning/intel/audit-residuals.md` or original audit document — P1 #11, P1 #14, P1 #15 source descriptions (if absent, retrieve from prior session intel)
- `src/content/index.ts:147` — fetch interception (P1 #11 target)
- `src/content/index.ts:99-109` — navigation URL tracking (P1 #14 target; module-level previousUrl needed)
- `src/content/index.ts:36-42` — rrweb buffer cleanup timestamps (P1 #15 target; epoch vs page-load-relative)

### Flake stabilization targets

- `.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-02-SUMMARY.md` "Issues Encountered" — A29 zip-mtime race documented; cs-injection-world is natural fix candidate
- `.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-03-SUMMARY.md` — A29 race confirmed pre-existing; not Plan 03-03 introduced
- `tests/uat/lib/harness-page-driver.ts` — findLatestZip at ~line 1395; A29 race-condition origin
- `tests/uat/extension-page-harness.ts` — assertA29 + assertA30 + assertA31 + (assertA32 absent — A32 is host-side only)
- `tests/uat/harness.test.ts` — orchestrator; driveA29Wrapped + driveA30Wrapped + driveA31Wrapped + driveA32 entries

### ROADMAP SC targets

- ROADMAP SC #1 — SW state persistence: src/background/index.ts (initialize + keepalivePort + chrome.runtime.onStartup); needs new harness for 5-min idle test
- ROADMAP SC #2 — fetch + XHR network_error: src/content/index.ts (setupFetchInterception + setupXHRInterception if exists); existing A30 covers click+input+navigation+js_error+network_error from synthetic triggers — Phase 4 SC #2 verifies the real-page failing-fetch path empirically
- ROADMAP SC #3 — generate-icons ESM/CJS: `generate-icons.js` at repo root; package.json `"type": "module"` setting
- ROADMAP SC #4 — dead-code grep: ripgrep for `permissions.request` + duplicate offscreen inline string in src/

### Visual polish targets

- `src/offscreen/recorder.ts` getDisplayMedia call site — add `video: { cursor: 'always' }` per Chrome `CursorCaptureConstraint` enum (per Plan 01-07 obs 2026-05-15)
- `src/shared/brand/mokosh-mark.svg` + `src/welcome/welcome.css` (or wherever logo renders on dark surfaces) — Plan 01-10 obs 2026-05-20

### CSP / build hygiene

- `vite.config.ts` — vite-plugin-node-polyfills config (setimmediate polyfill source)
- `dist/assets/index.ts-*.js` (SW chunk) — `new Function` source confirmation
- `.planning/phases/01-stabilize-video-pipeline/deferred-items.md` — pre-existing acceptance log

### Prior-phase pattern + closure precedents

- `.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-PATTERNS.md` — Approach B harness extension pattern (still canonical for new harness work in Phase 4)
- `.planning/phases/02-stabilize-export-pipeline/02-VERIFICATION.md` — T5 override template structure
- `.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md` — 4-override template structure
- `.planning/phases/01-stabilize-video-pipeline/01-13-SUMMARY.md` — Approach B harness ESTABLISHED
- `.planning/phases/02-stabilize-export-pipeline/02-04-SUMMARY.md` — A24-A28 extension precedent
- `.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-02-SUMMARY.md` — cs-injection-world pattern (chrome.tabs.create + chrome.scripting.executeScript world:'ISOLATED'); content-script realm access pattern

### Decision / operating-principle references

- `/home/parf/.claude/projects/-home-parf-projects-work-repremium/memory/feedback-trust-harness-over-manual-uat.md` — harness coverage canonical; operator empirical reserved for genuinely non-automatable cases (A32 RAM is the canonical example)
- `/home/parf/.claude/projects/-home-parf-projects-work-repremium/memory/feedback-pre-checkpoint-bundle-gates.md` — 6/6 standard inventory before checkpoint
- `/home/parf/.claude/projects/-home-parf-projects-work-repremium/memory/feedback-gsd-ceremony-for-fixes.md` — debug discoveries route through /gsd-debug; no hot-edits
- `/home/parf/.claude/projects/-home-parf-projects-work-repremium/memory/feedback-no-unilateral-scope-reduction.md` — user validates every scope decision

### Charter / v1 closure

- 2026-05-20 charter shift: "we don't care about privacy hardening" → REQ-password-confidentiality Out of Scope v1
- v1 close target: after Phase 4, fresh alpha re-distribution → tester confirmation → v1.0 tag + release notes (separate workstream; not Phase 4 plan)

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets

- **Approach B harness pattern**: any new Phase 4 harness assertions (A33+) follow the established assertA*/driveA*/orchestrator-wiring pattern. cs-injection-world adaptation (Plan 03-02) is canonical for content-script realm access.
- **findLatestZip helper** (`tests/uat/lib/harness-page-driver.ts:~1395`): mtime-sort archive selection; A29 race fix target.
- **JSZip + jszip helpers** (`tests/uat/lib/zip.ts`): archive parsing for harness assertions.
- **`__MOKOSH_UAT__` Vite-define-token**: gates test-only surfaces from production bundle (only needed if new FORBIDDEN_HOOK_STRINGS entries introduced).
- **Pre-checkpoint bundle gates pattern** (Plan 03-05 Task 1): 6/7 gate run before VERIFICATION.md write; Plan 04-08 (or equivalent closure plan) follows this.

### Established Patterns

- **Sequential wave decomposition for file-overlap** (Phase 2 lesson + Phase 3 lesson): plan-checker should catch `files_modified` overlap within a wave; if detected, decompose into sequential waves.
- **VERIFICATION.md override pattern**: documented in Phase 2 (T5) + Phase 3 (4 overrides incl. §10 #9). For Phase 4, success-criteria verification may use overrides for items already-verified-in-Phase-3 (e.g., dead-code grep is mostly checking work already done).
- **CON-event-log-schema** (REQUIREMENTS.md): 5 event types; Phase 4 P1 #11/#14/#15 fixes affect navigation + fetch fields without changing schema shape.
- **STATE.md CLI bug**: `state.record-session` flips status to "completed" when total/completed plan counts match — recurring bug fixed via manual STATE.md edit each phase (Phase 2 + Phase 3 + Phase 3-discuss + Phase 3-UI). Phase 4 will hit this 1-2 more times.

### Integration Points

- **src/content/index.ts**: locus of P1 #11/#14/#15 fixes; existing event-log emit + fetch interception + navigation tracking
- **src/background/index.ts**: locus of SW state persistence work (initialize + chrome.runtime.onStartup + keepalivePort)
- **src/offscreen/recorder.ts**: locus of getDisplayMedia cursor:'always' constraint
- **vite.config.ts** + dist/ build output: locus of setimmediate polyfill replacement
- **generate-icons.js** + package.json `"type": "module"`: locus of ESM/CJS compat fix

</code_context>

<specifics>
## Specific Ideas

- **A29 cs-injection-world fix:** the proven Plan 03-02 pattern — `chrome.tabs.create({url: 'https://example.com'})` + `chrome.scripting.executeScript({world: 'ISOLATED', target: {tabId}, func: probeDOMMutation})` — re-target the rrweb probe surface so the tab being captured is the harness probe page (eliminates iana.org leftover read race).
- **P1 #11 fetch fix specific code change:** `args[0]?.toString()` → `args[0] instanceof Request ? args[0].url : String(args[0])`. Add unit test for both string-arg and Request-arg cases.
- **P1 #14 nav URL fix:** add module-level `let previousUrl = location.href;` in src/content/index.ts; update before each navigation event capture; use `previousUrl` in the emit instead of `history.state?.url`.
- **P1 #15 rrweb timestamps:** use `Date.now()` at emit time as the canonical epoch source; rrweb's internal page-load-relative timestamps stay rrweb-internal but the wrapper writes Unix epoch for events.json.
- **5min idle test pattern (ROADMAP SC #1):** Puppeteer `await new Promise(r => setTimeout(r, 5 * 60 * 1000))` after recorder start; then SAVE; assert `video/last_30sec.webm` exists in archive + size > 0 bytes. Long-running test; consider parallel skip + dedicated CI lane.
- **fetch+XHR network_error verification (ROADMAP SC #2):** harness fires `fetch('https://example.com/does-not-exist-404')` + `xhr.open('GET', '/404-also'); xhr.send();` from probe HTML; SAVE; assert events.json contains 2 network_error entries with response code >= 400 (extends driveA30 or adds A33).
- **generate-icons ESM/CJS fix (SC #3):** package.json `"type": "module"` means `.js` files are ESM; generate-icons.js uses `require('fs')` (CJS); rename to `generate-icons.cjs` OR rewrite to ESM (`import fs from 'node:fs'`). Either works.
- **Dead-code grep (SC #4):** `rg 'permissions\.request' src/` should return 0 hits (was removed in Phase 1 Plan 01-05); `rg '<inline-offscreen-string>' src/ vite.config.ts` should return 0 hits (offscreen duality removed in Phase 1 Plan 01-06).
- **Cursor visibility implementation:** `getDisplayMedia({video: {cursor: 'always'}, audio: ...})` at the getDisplayMedia call site in src/offscreen/recorder.ts (or wherever the constraint is built per D-13 restart-segments architecture).
- **Dark-logo contrast:** options — (a) add `prefers-color-scheme: dark` media query in welcome.css with alt-color SVG, (b) swap to a contrast-tested neutral palette, (c) wrap logo in white-circle background. Designer-leaning; planner-side choice.
- **setimmediate polyfill replacement:** `globalThis.setImmediate = globalThis.setImmediate || ((fn, ...args) => queueMicrotask(() => fn(...args)));` inline in SW entry — bypasses vite-plugin-node-polyfills entirely.

</specifics>

<deferred>
## Deferred Ideas

### To v1.1 / v2 maintenance milestone (post-v1 close)

- **rrweb 2.0.0-alpha.4 → stable v2 upgrade** (D-P3-03 deferral; alpha-pin proven stable through 13 plans + 33/33 UAT GREEN; reaffirmed Phase 4 D-P4-01)
- **Programmatic SW-realm RAM measurement** via chrome.devtools Memory API (D-P3-04 deferral; reaffirmed Phase 4 D-P4-01; A32 best-effort + chrome://memory-internals + alpha distribution coverage accepted as v1 closure)
- **REQ-password-confidentiality v2 candidate**: full rrweb v2 maskInputFn + data-sensitive HTML attribute guards (D-P3-02 deferral; only revisit if charter reverses on "we don't care about privacy hardening")
- **Per-plan ROADMAP rows for any Phase 2/3 plans inline-tracked but not row-added** (if any beyond Plans 01-08..01-13 catch-up)
- **Alpha-tester findings integration**: any cycle-1-style debugs from current alpha (mokosh-build-2026-05-20-6dbed91.zip) or Phase 4 re-distributed build route through separate maintenance window — user explicitly handles operator-side signal out-of-band per D-P4-04

### To v2 / SRV milestone (per SPEC §9 Out of Scope)

- Server upload of captured archives (SRV-01)
- AI-driven diagnostics (SRV-02)
- Automatic ticket creation (SRV-03)
- Analytics dashboard (SRV-04)
- Audio recording (CAP-01)
- DOM-event masking + privacy hardening reactivation (if v2 charter reverses)

</deferred>

---

*Phase: 04-harden-clean-up-optional*
*Context gathered: 2026-05-20*
