Files
mokosh/.planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md
Mark c790c6a8b3 docs(04-06): complete visual polish + dark-logo decoupling — D-P4-03 closed (UAT 36/36 GREEN; 188/188 vitest with #9/#10 flake tolerated; operator re-confirmed 2026-05-26)
Plan 04-06 closure — the most ceremony-heavy plan in Phase 4: 3 planner
passes + 2 plan-checker passes + 4 task commits + 1 /gsd-debug fix cycle
+ this closure commit. D-P4-03 (locked, 04-CONTEXT.md) CLOSED — both
visual polish items: (a) cursor visibility verification + (b) dark-surface
logo contrast.

Closure trail:
  6a989e8 mis-diagnosed strict-meta-json deferred-items entry
  b59bd24 re-plan iter-1 — correct false jsdom premise + back-patch lines
  deb68df re-plan-checker iter-1 — ITERATE-NEEDED (2 BLOCKER)
  f3baa3a re-plan iter-2 — real A35 + corrected 184/184 baseline
  48c7053 re-plan-checker iter-2 — PASSED (0B + 0W + 3 cosmetic-advisories)
  f0b88d4 Task 1 — Wave 0 RED inline-SVG source-contract + cursor pin
  c416143 Task 2 — Wave 1 GREEN SVG+welcome.ts+globals.d.ts
  3f8e31a Task 3 — A35 driver + A17.8 narrowed + back-patch + correction
  d66cbf6 Task 4 artifact — operator-empirical screenshot harness
  (Task 4 first operator empirical: TWEAK verdict 2026-05-26)
  a8bcc17 debug-fix — decouple via --mks-mark-stroke + A35.5 sub-check
  (Task 4 re-empirical: CONFIRMED FIXED 2026-05-26)
  THIS    closure (SUMMARY + STATE.md + ROADMAP.md + debug archive)

Key deliverables:
- mokosh-mark.svg stroke="#181b2a" -> stroke="currentColor"
- welcome.ts ?url/<img> -> ?raw/DOMParser/replaceChildren inline-<svg>
- globals.d.ts *.svg?raw ambient decl
- src/shared/tokens.css NEW --mks-mark-stroke = var(--mks-linen-50) in :root
  (NOT overridden in .dark — theme-independent brand-component token)
- src/welcome/welcome.css .welcome-hero__mark rewired to --mks-mark-stroke
- NEW A35 host-side harness (5 sub-checks incl. A35.5 light+dark equality
  decouple-proof) at tests/uat/lib/harness-page-driver.ts
- A17.8 honestly narrowed to SOURCE-BUNDLING only; points to A35
- tests/welcome/inline-svg.test.ts (3 source-contract tests)
- tests/build/cursor-visibility.test.ts (1 regression pin)
- scripts/04-06-welcome-hero-screenshots.mjs (reproducible artifact)
- 01-07-SUMMARY back-patch (5 stale lines flipped; 4 historical left)
- deferred-items.md mis-diagnosis correction

Baselines preserved:
- vitest 188/188 GREEN (most recent 187/188 with 04-CONTEXT #9/#10
  webm-remux flake; passes in isolation; tolerated per Task 2 gate)
- UAT 36/36 GREEN; FORBIDDEN_HOOK_STRINGS unchanged at 12
- Pre-checkpoint bundle gates 6/6 PASS at both checkpoint + re-checkpoint
- All 4 ROADMAP SC CLOSED; D-P4-03 CLOSED

Phase 4 progress: 6/8 -> 7/8 (Plan 04-07 NEXT).

SUMMARY: .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md
Debug session archived: .planning/debug/resolved/04-06-dark-mode-mark-decouple.md
2026-05-26 13:14:41 +02:00

64 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, decisions, requirements-completed, duration, completed, tasks, files-modified
phase plan subsystem tags requires provides affects tech-stack key-files decisions requirements-completed duration completed tasks files-modified
04-harden-clean-up-optional 06 ui
visual-polish
dark-logo-contrast
ui-spec
currentColor
inline-svg
dom-parser
cursor-visibility-verification
charter-d-p4-03
operator-empirical
mks-mark-stroke-token
theme-decoupling
live-dom-harness
a35
a17-8-narrowed
01-07-summary-back-patch
gsd-debug-routed-fix
multi-iteration-ceremony
phase provides
01-stabilize-video-pipeline Plan 01-09 opportunistically shipped `cursor: 'always'` at src/offscreen/recorder.ts:285 (D-01 getDisplayMedia constraints object). Plan 01-10 bundled `mokosh-mark.svg` via Vite `?url` import + populateMark `<img>` injection (the LIGHT-surface contrast baseline). Plan 01-12 D-04 Loom palette (`--mks-linen-50` = #faf7f1; `--mks-madder-600` = #b2543d; `--mks-ink-900` = #181b2a) + `--mks-fg-inverse` semantic token that flips linen-50 -> ink-900 in `.dark, [data-theme="dark"]`. Plan 04-06 inverts the welcome-mark strategy from `?url`/`<img>` (Plan 01-10) to `?raw`/inline-`<svg>`/currentColor + introduces a NEW brand-component token `--mks-mark-stroke` to decouple from the theme-flipping semantic token.
plan provides
04-03 A29 cs-injection-world rewrite established the host-side driver pattern (driveA29/30/31/32/33/34) — A35 follows this pattern verbatim (host-side; build CheckRecord[] directly; no page.evaluate(window.__mokoshHarness) wrapper).
plan provides
04-05 driveA34 host-side driver template + 3-site orchestrator wiring pattern (import + driveA{N}Wrapped + drivers-array push). A35 inserts immediately after A34 with the same wiring shape.
plan provides
04-08 A33 SW state persistence baseline (UAT 34/34 GREEN; vitest 184/184 GREEN; Tier-1 FORBIDDEN_HOOK_STRINGS at 12). Plan 04-06 flips UAT 35 -> 36 (A35 + A35.5) and vitest 184 -> 188 (3 inline-svg.test.ts + 1 cursor-visibility.test.ts).
Dark-logo contrast strategy landed end-to-end: SVG stroke recolor (`stroke="currentColor"`) + welcome.ts `?raw` import + DOMParser-based inline-SVG injection (no `<img>`, no innerHTML, no eval) + globals.d.ts `*.svg?raw` ambient module declaration + theme-INDEPENDENT brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in :root (NOT overridden in `.dark, [data-theme="dark"]`).
Empirical closure of D-P4-03 (locked, 04-CONTEXT.md): BOTH (a) cursor visibility AND (b) dark-surface logo contrast. (a) verified by tests/build/cursor-visibility.test.ts file-grep regression pin (already-shipped Plan 01-09 literal at recorder.ts:285) + 01-07-SUMMARY.md back-patch (5 stale 'deferred to Phase 5' lines flipped). (b) verified by 4-layer coverage: source-contract unit tests (3 RED -> GREEN in inline-svg.test.ts) + A17.8 raw-source bundling check (narrowed honestly) + NEW A35 live-DOM host-side harness assertion (5 sub-checks including the A35.5 light+dark equality decouple-proof) + operator-empirical Puppeteer screenshots (TWEAK -> /gsd-debug fix -> CONFIRMED FIXED 2026-05-26).
NEW host-side A35 harness assertion at tests/uat/lib/harness-page-driver.ts — opens welcome.html as a real Puppeteer tab (`browser.newPage()` + `page.goto('chrome-extension://${extensionId}/src/welcome/welcome.html')`), lets `populateMark()` run at DOMContentLoaded, reads the LIVE injected `.welcome-hero__mark svg` element + `getComputedStyle().stroke` to prove the `currentColor` cascade actually resolves. Now probes BOTH light + dark themes (data-theme="dark" toggle on documentElement) via an extracted `a35Probe(welcomePage, dark)` helper and asserts A35.5: `light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)'` (linen-50). This is the genuine automated proof of the runtime injection + cascade + theme decoupling — closes iter-2 BLOCKER 1.
A17.8 sub-check honestly narrowed at tests/uat/extension-page-harness.ts: replaced the prior `data:image/svg+xml` data-URL grep with a raw-source grep (`stroke="currentColor"` + `viewBox="0 0 32 32"` in jsText). A17.8 is now a SOURCE-BUNDLING check only — disavows live-DOM coverage and explicitly points to A35 for runtime behavior proof. No more fictitious delegation.
NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in src/shared/tokens.css universal :root block (line ~131) + `.welcome-hero__mark { color: var(--mks-mark-stroke); }` rewire at src/welcome/welcome.css line 72. CRUCIALLY this token is NOT overridden in the `.dark, [data-theme="dark"]` block — stays linen-50 on every surface. The previous semantic-token coupling (`color: var(--mks-fg-inverse)`) was the wrong abstraction (the welcome-mark wrapper is theme-INDEPENDENT madder-600, not a theme-flipping inverse surface).
Operator-empirical screenshot harness at scripts/04-06-welcome-hero-screenshots.mjs (194 lines) — Puppeteer script that loads dist/ as an unpacked extension, opens welcome.html, and captures `/tmp/04-06-welcome-hero-light.png` + `/tmp/04-06-welcome-hero-dark.png` (dark via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`). Reproducible artifact for any future re-shoot if the strategy ever needs re-validation.
01-07-SUMMARY.md back-patch — 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09; verified Phase 4 Plan 04-06'; 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged. The Phase 1 closure record is now internally consistent with the reality (cursor:'always' was shipped 2026-05-19 in Plan 01-09 commit `a2dfc8c` co-land, not deferred).
deferred-items.md correction — the prior 'tests/build/strict-meta-json-validation.test.ts fails on a clean tree' mis-diagnosis (commit 6a989e8) rewritten to describe the real root cause: the pre-existing 04-CONTEXT.md in-scope items #9 (parallel-vitest Tier-1-build-step race; ~1/5 runs) + #10 (2 ffprobe/ffmpeg vitest flakes) non-deterministic family. The true clean baseline is 184/184 GREEN (188/188 after Plan 04-06).
ROADMAP Plan 04-06 row flipped `[ ]` -> `[x]` with closure annotation. Phase 4 progress 6/8 -> 7/8.
D-P4-03 (charter, locked) — both visual polish items closed via Plan 04-06.
Plan 04-07 (Phase 4 closure aggregator) is now unblocked — only remaining Phase 4 plan.
UAT harness driver count 35 -> 36 (A35 appended LAST after A34; A35 itself contains 5 sub-checks now, including A35.5 decouple-proof). Per-plan increments: Plan 04-08 (33->34, A33), Plan 04-05 (34->35, A34), Plan 04-06 (35->36, A35).
vitest baseline 184/184 -> 188/188 (+4: 3 inline-svg.test.ts source-contract tests + 1 cursor-visibility.test.ts regression pin). The pre-existing 04-CONTEXT #9/#10 parallel-vitest flake family remains tolerable in full-suite runs (currently 187/188 in the most recent post-fix run — webm-remux flake fired; passes in isolation; documented).
Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 — Plan 04-06 introduces no `__MOKOSH_UAT__`-gated symbols (DOMParser is a standard web platform API; `?raw` is a normal production Vite import; `*.svg?raw` ambient decl is type-only).
Pre-checkpoint bundle gates 6/6 PASS unchanged from Plan 04-05 baseline (SW chunk byte-identical at boundary: Plan 04-06 modifies only src/welcome/* + src/shared/brand/* + src/shared/tokens.css :root token + tests/* + globals.d.ts; SW chunk shape unchanged).
Cross-plan provenance: this plan went through 3 planner passes + 2 plan-checker passes + 1 /gsd-debug fix cycle — the most ceremony-heavy plan in Phase 4. Full audit trail: `6a989e8` (mis-diagnosis) -> `b59bd24` (re-plan iter-1) -> `deb68df` (checker iter-1 ITERATE-NEEDED) -> `f3baa3a` (re-plan iter-2) -> `48c7053` (checker iter-2 PASSED) -> `f0b88d4`/`c416143`/`3f8e31a` (Tasks 1-3) -> `d66cbf6` (screenshot harness) -> operator TWEAK -> `a8bcc17` (debug fix) -> operator CONFIRMED FIXED 2026-05-26.
added patterns
Inline-SVG via Vite `?raw` import + DOMParser + replaceChildren (NEW for Plan 04-06): `import markSvg from '../shared/brand/mokosh-mark.svg?raw';` returns the SVG source as a string at build time; `new DOMParser().parseFromString(markSvg, 'image/svg+xml').documentElement` produces a live `<svg>` element; `slot.replaceChildren(svg)` injects it into the DOM. Inline `<svg>` inherits parent CSS `color` (W3C SVG2 §13.3); `stroke="currentColor"` resolves to that inherited color. NEVER use `innerHTML` (MV3 CSP discipline; T-04-06-01 mitigation). Replaces the Plan 01-10 `?url`/`<img>` pattern for ANY future case where the brand mark must adapt to its surface via CSS cascade.
Brand-component token vs semantic token abstraction (NEW for Plan 04-06): when a CSS color needs to be theme-INDEPENDENT on a theme-INDEPENDENT surface, do NOT couple to a semantic token that flips with theme (e.g. `--mks-fg-inverse`). Introduce a dedicated brand-component token (e.g. `--mks-mark-stroke`) in the universal `:root` block and do NOT override it in `.dark, [data-theme="dark"]`. The semantic token is for text-foreground-on-inverse-surface (where the surface flips); the brand-component token is for the canonical brand color on a fixed surface. Established at src/shared/tokens.css :root + src/welcome/welcome.css:72.
Live-DOM host-side harness assertion against welcome.html (NEW for Plan 04-06): when a runtime DOM behavior (populateMark injection + currentColor cascade resolution) cannot be verified by a node-env unit test (vitest is `environment: 'node'`; no DOM library) AND cannot be verified by the existing A17 string-grep (detached DOMParser parse of the welcome HTML), open the welcome page as a NEW Puppeteer tab via `browser.newPage()` + `page.goto('chrome-extension://${extensionId}/src/welcome/welcome.html', { waitUntil: 'domcontentloaded' })`. Run `page.waitForSelector(...)` for race-free guard against the readyState branch. Read live DOM via `page.evaluate(...) { ... }` returning a plain-object result. Build CheckRecord[] host-side; close the welcomePage in a finally block. Established at tests/uat/lib/harness-page-driver.ts driveA35; pattern reusable for any future welcome.html DOM contract.
Multi-theme live-DOM probe (NEW for Plan 04-06): a single host-side driver probes BOTH light + dark themes by toggling `documentElement.setAttribute('data-theme', 'dark' | 'light')` between two `page.evaluate()` calls (and waiting `requestAnimationFrame()` for the CSS cascade to recompute). Extract the per-theme probe into a helper (e.g. `a35Probe(welcomePage, dark)`) to keep the driver body symmetric. Useful for any future driver that needs to assert theme decoupling or theme symmetry. Established at tests/uat/lib/harness-page-driver.ts driveA35 + a35Probe helper.
Operator-empirical screenshot harness pattern (NEW for Plan 04-06): per `feedback-trust-harness-over-manual-uat.md`, when the operator needs to judge aesthetics (e.g. dark-mode contrast), produce light + dark screenshots via Puppeteer's `Emulation.setEmulatedMedia` for `prefers-color-scheme: dark` rather than asking the operator to click through Chrome. Save to `/tmp/<plan>-<scope>-{light,dark}.png` and surface both paths in the checkpoint. Operator only judges the visual artifact; automation does the rest. Established at scripts/04-06-welcome-hero-screenshots.mjs.
created modified
tests/welcome/inline-svg.test.ts (70 lines; 3 it() blocks in `UI-SPEC dark-logo currentColor strategy — source-level contract`; node-env; file-read + string-assert pattern per tests/i18n/manifest-i18n.test.ts; NO DOM library import; NO `@vitest-environment jsdom` directive). Tests: A (mokosh-mark.svg recolor: contains `stroke="currentColor"` + `viewBox="0 0 32 32"`; does NOT contain `stroke="#181b2a"`); B (welcome.ts ?raw + DOMParser + replaceChildren; does NOT contain `?url` or `innerHTML`); C (globals.d.ts ambient `*.svg?raw` decl).
tests/build/cursor-visibility.test.ts (45 lines; 1 it() block; node-env file-grep regression pin for the literal `cursor: 'always'` at src/offscreen/recorder.ts:285 — already shipped opportunistically by Plan 01-09).
scripts/04-06-welcome-hero-screenshots.mjs (194 lines; Puppeteer; loads dist/ as unpacked extension; opens chrome-extension://${extensionId}/src/welcome/welcome.html; captures `/tmp/04-06-welcome-hero-light.png` + `/tmp/04-06-welcome-hero-dark.png` via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`; prints the resolved `getComputedStyle().stroke` for each theme as diagnostic output). Reproducible verification artifact for the operator-empirical Task 4 checkpoint.
.planning/debug/04-06-dark-mode-mark-decouple.md -> RESOLVED, archived to .planning/debug/resolved/ at plan closure. 115 lines; full reasoning checkpoint + symptoms + evidence + resolution; cites the abstraction-error root cause + the --mks-mark-stroke brand-component token fix.
.planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md (this file).
src/shared/brand/mokosh-mark.svg — single-attribute change on root `<svg>` (line 2): `stroke="#181b2a"` -> `stroke="currentColor"`. 12 `<line>` + 1 `<rect>` children inherit stroke from the root and are UNCHANGED (they were verified line-by-line; no other attributes touched). +1 / -1.
src/welcome/welcome.ts — line 46 import flipped from `import markUrl from '../shared/brand/mokosh-mark.svg?url';` to `import markSvg from '../shared/brand/mokosh-mark.svg?raw';`. populateMark function body (lines 159-179) rewritten: `<img>` injection REPLACED with DOMParser + replaceChildren inline-SVG injection. Preserves the function signature, the slots query, the altText resolution (`COPY['welcome.hero.mark.alt'] ?? 'Знак Mokosh'`), the filter-pipeline form, and the empty-slot logger.warn fallback. NEVER uses innerHTML (MV3 CSP discipline; T-04-06-01). Docstring (lines 134-158) + comment block (lines 36-45) updated to describe the inline-`<svg>` strategy + cite the UI-SPEC implementation amendment + cite the currentColor cascade requirement. Net +90 / -25 lines.
globals.d.ts — appended `declare module '*.svg?raw' { const raw: string; export default raw; }` ambient module declaration block immediately after the existing `*.svg?url` block (lines 34-37). Explanatory comment per project docstring convention cites the UI-SPEC dark-logo strategy + Vite `?raw` asset-as-string semantics. +21 lines.
src/shared/tokens.css — added the NEW brand-component token `--mks-mark-stroke: var(--mks-linen-50);` adjacent to `--mks-fg-inverse` in the universal `:root` block (line ~131). CRUCIALLY this token is NOT added to the `.dark, [data-theme="dark"]` block — it stays linen-50 on every surface. Comment in source cites the abstraction-error rationale + the .planning/debug/04-06-dark-mode-mark-decouple.md debug note. SCOPE EXPANSION (authorized by Task 4 TWEAK verdict) — this file was not in Plan 04-06 re-plan iter-2 `files_modified`. +15 lines.
src/welcome/welcome.css — line 72 flipped from `color: var(--mks-fg-inverse);` to `color: var(--mks-mark-stroke);` inside the `.welcome-hero__mark` block. The bare `.welcome-hero__mark-img` class selector at line 91 is unchanged (matches `<svg>` and `<img>` identically — no `img.` qualifier). Comment block updated to cite the cascade chain (wrapper color -> inline `<svg>` stroke via currentColor). SCOPE EXPANSION (authorized by Task 4 TWEAK verdict). +11 / -0 lines.
tests/uat/extension-page-harness.ts — A17.8 sub-check (lines ~2249-2298) narrowed honestly: replaced `hasInlineDataUrl` (jsText.includes('data:image/svg+xml')) + svgFileUrl regex with a raw-source grep that asserts jsText contains `stroke="currentColor"` AND `viewBox="0 0 32 32"`. Explanatory comment block flipped to state honestly: A17.8 is a SOURCE-BUNDLING check (confirms `?raw` inlined the source string); it does NOT prove live-DOM injection or that the currentColor cascade resolves — that runtime behavior is verified by the NEW host-side assertion A35. Net +25 / -45 lines.
tests/uat/lib/harness-page-driver.ts — appended NEW `driveA35` host-side driver function after `driveA34` (+247 lines). Signature: `(page: Page, browser: Browser, extensionId: string) => Promise<AssertionRecord>`. Opens welcome.html via `browser.newPage()` + `page.goto(chrome-extension://${extensionId}/src/welcome/welcome.html, { waitUntil: 'domcontentloaded' })` + `page.waitForSelector('.welcome-hero__mark svg', ...)`. Extracted `a35Probe(welcomePage, dark)` helper that toggles `data-theme="dark"` on documentElement and reads the LIVE injected `.welcome-hero__mark svg` (svgPresent + strokeAttr + computedStroke + imgPresent). 5 CheckRecords: A35.1 svg present (light); A35.2 stroke="currentColor" (light); A35.3 getComputedStyle().stroke is the non-default linen-50 (light); A35.4 no `<img>` in slot; A35.5 (NEW from debug session) light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)' (linen-50 decouple-proof). welcomePage.close() in finally block ensures no tab leak. Try/catch + result.error mirror driveA33/driveA34.
tests/uat/harness.test.ts — 3-site lockstep wiring (+28 lines): (1) `driveA35` added to the import block from './lib/harness-page-driver' (alongside driveA33/driveA34, lines 81-116); (2) `driveA35Wrapped` closure added alongside driveA33Wrapped/driveA34Wrapped (lines 367-372) — captures `handles.browser` + `handles.extensionId`; (3) `{ name: 'A35', drive: driveA35Wrapped }` appended as the LAST entry of the `drivers` array (immediately after the A34 entry, line ~534). Architecture banner string at line 283 left for cosmetic-advisory ADV-2A choice (auto-count via `total = drivers.length + 1` at line 580 carries the actual count). A35 is NOT env-gated.
.planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md — surgical back-patch: 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically in Plan 01-09 at recorder.ts:285; verified in Phase 4 Plan 04-06'; 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged (they accurately describe what the Phase-1-closure commits recorded at the time). After the flips, the SUMMARY narrative is internally consistent. Net +10 / -10 lines.
.planning/phases/04-harden-clean-up-optional/deferred-items.md — corrected the prior 'strict-meta-json fails on a clean tree' mis-diagnosis (commit 6a989e8). The entry now correctly describes the 04-CONTEXT #9/#10 parallel-vitest ffprobe flake family + states the true clean baseline is 184/184 GREEN (188/188 after Plan 04-06). +2 / -2 lines.
.planning/ROADMAP.md — Plan 04-06 row flipped `[ ]` -> `[x]` with closure annotation (D-P4-03 closed; --mks-mark-stroke decoupling + A35.5 light+dark equality; operator-empirical re-confirmed 2026-05-26; UAT 35->36; vitest 184->188). Phase 4 progress table cell updated from `6/8 In Progress (Plan 04-05 closed ROADMAP SC #2)` to `7/8 In Progress (Plan 04-06 closed D-P4-03)`.
.planning/STATE.md — Current Position advanced to Plan 7 of 7 (04-07 NEXT). Stopped At updated. Last activity 2026-05-26. Plan 04-06 closure section added. Decisions list appended. Performance metric row added. Progress flipped 90% -> 93% (28/31 -> 29/31).
Multi-iteration ceremony — Plan 04-06 went through 3 planner passes + 2 plan-checker passes + 1 /gsd-debug fix cycle. This is the most ceremony-heavy plan in Phase 4 and worth full provenance traceability. Sequence: original plan (BLOCKED at Task 1 on false jsdom premise) -> mis-diagnosis logged at `6a989e8` (`deferred-items.md` entry naming strict-meta-json wrongly; later corrected in this plan's Task 3) -> re-plan iter-1 `b59bd24` (correct false jsdom premise + back-patch lines) -> re-plan-checker iter-1 `deb68df` (ITERATE-NEEDED: 2 BLOCKER — fictitious A17.8 delegation + wrong vitest baseline) -> re-plan iter-2 `f3baa3a` (real A35 + behavior-based vitest gate + 184/184 baseline) -> re-plan-checker iter-2 `48c7053` (PASSED: 0B + 0W + 3 cosmetic-advisories ADV-2A/B/C) -> Tasks 1-3 executed cleanly (`f0b88d4` + `c416143` + `3f8e31a`) -> operator-empirical screenshot harness landed (`d66cbf6`) -> Task 4 operator-empirical checkpoint -> TWEAK verdict (dark cascade flipped icon to ink-900 indigo on madder; lower contrast than light) -> /gsd-debug session (.planning/debug/04-06-dark-mode-mark-decouple.md) -> fix commit `a8bcc17` (decouple via --mks-mark-stroke token + A35.5 light+dark equality sub-check) -> operator re-empirical 2026-05-26: 'Confirmed fixed — close Plan 04-06'. Lesson: when a checkpoint is operator-empirical, the planner should anticipate the abstraction-error vector (semantic vs brand-component tokens) and either (a) front-load the brand-component token into the plan, or (b) accept that a /gsd-debug fix cycle is part of the plan budget.
Brand-component token vs semantic token (the debug-session abstraction-error correction) — `--mks-fg-inverse` is a SEMANTIC text-foreground-on-inverse-surface token (`tokens.css :root` line 128 = linen-50; `.dark` line 244 = ink-900). It was the wrong coupling for `.welcome-hero__mark`: the welcome mark sits on a theme-INDEPENDENT madder-600 circle (the wrapper background is fixed; the surface does NOT flip with theme). When the theme flipped, the `currentColor` cascade resolved to ink-900 (indigo) on madder — muddy, low contrast. The fix: introduce a NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in the universal `:root` block + do NOT override it in `.dark, [data-theme="dark"]` + rewire `.welcome-hero__mark` to `color: var(--mks-mark-stroke);`. The SVG remains untouched — `stroke="currentColor"` cascade plumbing identical; only the wrapper's color source changed. Both themes now resolve `computedStroke` to `rgb(250, 247, 241)` (linen-50) — crisp linen-on-madder in both themes. Lesson encoded as a NEW pattern: brand-component tokens for theme-independent surfaces; semantic tokens for theme-flipping surfaces. The `--mks-fg-inverse` token continues to serve its proper role (e.g. src/popup/style.css:39, which IS a theme-flipping surface — LEFT untouched).
A35.5 sub-check (NEW from debug session) — the post-debug A35 driver was strengthened with a 5th sub-check: assert `light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)'` (linen-50). This is the DECOUPLE-PROOF: a future regression that re-couples the stroke to a theme-flipping token would fail A35.5 even if A35.1-A35.4 all pass (a coupled stroke still injects the inline `<svg>`; it just renders the wrong color in dark). A35.5 is the canonical assertion for any future case where a brand-component must remain theme-independent. The probe pattern (extracted `a35Probe(welcomePage, dark)` helper; toggle data-theme on documentElement; wait requestAnimationFrame; re-read getComputedStyle) is reusable for any future multi-theme live-DOM check.
Honest A17.8 narrowing — the iter-1 re-plan claimed 'live-DOM injection is delegated to A17.8 in real Chrome', which iter-1 BLOCKER 1 verified FALSE (A17.8 is 100% string-grep on jsText via a detached DOMParser parse; populateMark() never runs in the harness). The iter-2 fix is the NEW A35 host-side driver (real new harness work); A17.8 is honestly narrowed to a SOURCE-BUNDLING check only. The A17.8 explanatory comment block now disavows live-DOM coverage and explicitly points to A35. No more fictitious delegation. Lesson: when test coverage is split across layers (source/bundling/live-DOM), every assertion's comment block must accurately describe its scope. The 'delegate to X' shorthand is a code smell unless X actually does the work.
Behavior-based vitest gate logic (iter-2 BLOCKER 2 resolution) — the iter-1 plan hard-coded 'expected failing test == strict-meta-json-validation.test.ts'. iter-2 BLOCKER 2 verified that test is 8/8 GREEN in isolation AND on a clean tree. The real flake is the 04-CONTEXT #9/#10 parallel-vitest ffprobe-timeout family, which lands non-deterministically on whichever test loses the worker race (observed: tests/background/webm-remux.test.ts). The iter-2 gate logic is behavior-based: 188/188 GREEN -> PASS; 187/1 where the single failing test PASSES in isolation -> tolerated 04-CONTEXT #9/#10 flake, PASS, record the test name + isolation re-run result in SUMMARY; 2+ failures OR a reproducible single failure -> FAIL the gate. The gate hard-codes NO test filename — it distinguishes flake from regression via re-run behavior. Plan 04-06 baseline is 184/184 GREEN; target after this plan is 188/188 (+4 new tests). Lesson: never hard-code a specific failing test name; always gate on behavior.
01-07-SUMMARY back-patch — 5 lines flipped (22/47/82/135/205) and 4 lines left (40/89/109/110). The 5 flipped lines carry the genuine stale 'deferred to Phase 5' framing — `cursor: 'always'` actually shipped in Plan 01-09 at recorder.ts:285 (commit `a2dfc8c` co-land 2026-05-19), so the deferral framing was false. The 4 left lines are historical descriptions of what specific Phase-1-closure commits DID (the ROADMAP modification, the 7df72aa commit, the decision-log tag) — those are accurate historical records and must not be rewritten. The classification was verified iter-2 by re-running `grep -nE 'Phase 5|deferred to Phase 5' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md`. Lesson: when back-patching historical documents, distinguish forward-looking framing (FLIP) from historical commit-content records (LEAVE).
Cosmetic advisory ADV-2A resolution (architecture banner string at harness.test.ts:283) — the plan instructed `Append ', A35' to the architecture banner string`. The banner today reads `...A29, A30, A31, A32)` — A33 and A34 are MISSING from this banner (they were added to the drivers array but the banner was not updated). The auto-count via `total = drivers.length + 1` (line 580) carries the actual count, so the banner is cosmetic display only. Decision: LEAVE the banner alone in Plan 04-06 — adding ', A35' alongside the missing A33/A34 would either understate (', A35' alone is misleading) or scope-creep (', A33, A34, A35' updates 3 plans' worth of cosmetic display). Non-blocking — no gate depends on the banner string. Plan 04-07 (Phase 4 closure aggregator) may opportunistically refresh the banner if it touches that file.
Cosmetic advisory ADV-2B resolution (SKIP_PROD_REBUILD=0 rationale) — the plan said `SKIP_PROD_REBUILD=0 is intentional: the harness must rebuild dist-test`. Actually `SKIP_PROD_REBUILD` gates the prod `dist/` build (used by A0 hook-string grep gate); `dist-test/` always rebuilds because `package.json:12` runs `build:test` unconditionally. The actual reason SKIP_PROD_REBUILD=0 is needed is the A0 grep-gate against fresh `dist/`. Decision: keep the command shape `SKIP_PROD_REBUILD=0`, document the corrected rationale here. Executor behavior is unchanged — the command produces the right result. Non-blocking prose-accuracy fix.
Cosmetic advisory ADV-2C resolution (race-analysis underspecified in threat model) — the plan's threat model T-04-06 row noted finally-block cleanup but did not explicitly note that A35 is appended LAST in the drivers array. Verified independently safe: drivers run serially (harness.test.ts:542 `for (...)` loop, not Promise.all); A35 is last; A35 does NOT mutate chrome.storage (opening welcome.html does NOT trigger openWelcomeIfFirstInstall — that runs only on chrome.runtime.onInstalled); welcomePage.close() in finally ensures no tab leak. Pollution-of-future-drivers is moot (there are none). Decision: document the safety here in SUMMARY; no design change. Non-blocking documentation clarity.
~4 days end-to-end (3 planner passes + 2 checker passes + 4 task commits + 1 debug fix; sequential foreground with operator empirical pause in the middle) 2026-05-26 4 11

Phase 04 Plan 06: Dark-Logo Contrast + Inline-SVG Injection + Cursor Visibility Verification + D-P4-03 Closure Summary

Dark-logo currentColor strategy + theme-independent --mks-mark-stroke brand-component token + DOMParser inline-SVG injection (no <img>, no innerHTML) + NEW A35 live-DOM host-side harness with 5-sub-check decouple-proof + cursor-visibility regression pin + 01-07-SUMMARY back-patch — closes D-P4-03 (both visual polish items) after 3 planner passes + 2 checker passes + 1 /gsd-debug fix cycle.

Performance

  • Duration: ~4 days end-to-end (executor wall-clock ~6h across 4 task commits + 1 debug-fix commit; operator-empirical pause for the dark-mode aesthetic judgment + debug fix re-confirmation)
  • Started: 2026-05-22 (executor spawn after re-plan-checker iter-2 PASSED at 48c7053)
  • Completed: 2026-05-26 (operator re-empirical confirmation "Confirmed fixed — close Plan 04-06")
  • Tasks: 4 of 4 plan tasks complete (Tasks 1-3 autonomous + Task 4 operator-empirical UAT with TWEAK -> debug-fix -> CONFIRMED FIXED arc)
  • Files modified: 11 (3 source files + 4 test files + 2 spec docs + STATE.md + ROADMAP.md) + 1 created script (operator-empirical screenshot harness) + 1 archived debug note
  • Production source changes: 3 files (src/shared/brand/mokosh-mark.svg + src/welcome/welcome.ts + src/welcome/welcome.css + src/shared/tokens.css — the latter two are SCOPE EXPANSIONS authorized by the Task 4 TWEAK verdict)

Accomplishments

  • Dark-logo currentColor strategy landed end-to-end (Task 2 commit c416143): SVG stroke recolor (stroke=\"currentColor\") + welcome.ts ?raw import + DOMParser-based inline-SVG injection (no <img>, no innerHTML, no eval) + globals.d.ts *.svg?raw ambient module declaration. Inline <svg> inherits parent CSS color via W3C SVG2 §13.3 cascade; stroke=\"currentColor\" resolves to that inherited color. MV3 CSP discipline preserved (T-04-06-01 mitigation).
  • Theme decoupling via --mks-mark-stroke brand-component token (debug-fix commit a8bcc17): introduced a NEW brand-component token --mks-mark-stroke = var(--mks-linen-50) in the universal :root block of src/shared/tokens.css. CRUCIALLY this token is NOT overridden in .dark, [data-theme=\"dark\"] — stays linen-50 on every surface. Rewired .welcome-hero__mark { color: var(--mks-mark-stroke); } at src/welcome/welcome.css line 72. Both light and dark themes now resolve computedStroke to rgb(250, 247, 241) (linen-50) — crisp linen-on-madder mark in both themes. The SVG remains untouched.
  • NEW host-side A35 harness assertion (Task 3 commit 3f8e31a + debug-fix commit a8bcc17 strengthening): opens welcome.html as a real Puppeteer tab; lets populateMark() run at DOMContentLoaded; reads the LIVE injected .welcome-hero__mark svg element + getComputedStyle().stroke. 5 CheckRecords: A35.1 svg present; A35.2 stroke="currentColor"; A35.3 getComputedStyle().stroke resolved non-default; A35.4 no <img> in slot; A35.5 (NEW from debug) light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)' (the decouple-proof). welcomePage.close() in finally block; UAT 36/36 GREEN.
  • Honest A17.8 narrowing (Task 3 commit 3f8e31a): replaced the data:image/svg+xml data-URL grep with a raw-source grep (stroke=\"currentColor\" + viewBox=\"0 0 32 32\" in jsText). A17.8 is now a SOURCE-BUNDLING check only — the explanatory comment block explicitly disavows live-DOM coverage and points to A35 for runtime behavior proof.
  • Source-contract unit tests (Task 1 commit f0b88d4): tests/welcome/inline-svg.test.ts (3 tests; node-env file-read + string-assert; NO DOM library; NO @vitest-environment jsdom). Tests pin source TEXT: SVG recolor + welcome.ts ?raw/DOMParser/replaceChildren/no-innerHTML + globals.d.ts ambient decl. 3 RED today -> 3 GREEN after Task 2.
  • Cursor-visibility regression pin (Task 1 commit f0b88d4): tests/build/cursor-visibility.test.ts (1 test; node-env file-grep) regression-pins the literal cursor: 'always' at src/offscreen/recorder.ts:285 (already shipped opportunistically by Plan 01-09). 1 GREEN-on-arrival.
  • 01-07-SUMMARY back-patch (Task 3 commit 3f8e31a): 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09 at recorder.ts:285; verified Phase 4 Plan 04-06'. 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged.
  • deferred-items.md mis-diagnosis correction (Task 3 commit 3f8e31a): the prior 'strict-meta-json fails on a clean tree' entry (commit 6a989e8) rewritten to describe the real root cause — the 04-CONTEXT #9/#10 parallel-vitest ffprobe flake family. True clean baseline corrected to 184/184 GREEN.
  • Operator-empirical screenshot harness (commit d66cbf6): scripts/04-06-welcome-hero-screenshots.mjs (194 lines) — reproducible Puppeteer script that captures /tmp/04-06-welcome-hero-{light,dark}.png via Emulation.setEmulatedMedia prefers-color-scheme: dark. Re-runnable for any future re-validation.
  • Operator-empirical Task 4 closure: TWEAK on 2026-05-26 (dark cascade flipped icon to ink-900 indigo on madder; lower contrast than light) -> /gsd-debug session with abstraction-error verdict -> fix commit a8bcc17 (decouple via --mks-mark-stroke token + A35.5 light+dark equality sub-check) -> operator re-empirical confirmation "Confirmed fixed — close Plan 04-06".
  • D-P4-03 (charter, locked) closed end-to-end: BOTH (a) cursor visibility AND (b) dark-surface logo contrast. (a) verified by tests/build/cursor-visibility.test.ts + 01-07-SUMMARY back-patch. (b) verified by 4-layer coverage: source-contract unit tests + A17.8 raw-source grep + A35 live-DOM (5 sub-checks including A35.5 decouple-proof) + operator-empirical screenshots.

Multi-Iteration Ceremony History

Plan 04-06 is the most ceremony-heavy plan in Phase 4 — full provenance traceability:

Commit Date Stage
6a989e8 2026-05-22 Mis-diagnosed strict-meta-json deferred-items entry (logged out-of-scope from initial executor BLOCK on false jsdom premise)
b59bd24 2026-05-22 Re-plan iter-1 — correct false jsdom premise + stale back-patch lines + baseline
deb68df 2026-05-22 Re-plan-checker iter-1 — ITERATE-NEEDED (2 BLOCKER: fictitious A17.8 delegation + wrong vitest baseline)
f3baa3a 2026-05-22 Re-plan iter-2 — real inline-SVG coverage (NEW A35) + corrected 184/184 baseline
48c7053 2026-05-22 Re-plan-checker iter-2 — PASSED (0 BLOCKER + 0 WARNING + 3 cosmetic-advisories ADV-2A/B/C)
f0b88d4 2026-05-26 Task 1 — Wave 0 RED: inline-SVG source-contract + cursor-visibility regression pin
c416143 2026-05-26 Task 2 — Wave 1 GREEN: dark-logo currentColor + inline-SVG injection
3f8e31a 2026-05-26 Task 3 — A35 live-DOM inline-SVG harness + A17.8 raw-source update + back-patch
d66cbf6 2026-05-26 Operator-empirical screenshot harness (Task 4 artifact)
(TWEAK) 2026-05-26 Task 4 operator-empirical UAT verdict: dark cascade flipped icon to ink-900 indigo
a8bcc17 2026-05-26 /gsd-debug fix: decouple via --mks-mark-stroke token + A35.5 light+dark equality
(CONFIRM) 2026-05-26 Operator re-empirical: "Confirmed fixed — close Plan 04-06"
(THIS) 2026-05-26 Closure commit (SUMMARY + STATE.md + ROADMAP.md + debug archive)

Provenance lesson: when a checkpoint is operator-empirical, the planner should either (a) front-load the brand-component token into the plan, or (b) accept that a /gsd-debug fix cycle is part of the plan budget. Plan 04-06's checker iter-2 PASSED verdict was sound on the technical contract (the SVG/welcome.ts/globals.d.ts/A35 strategy was correct); the abstraction-error vector (semantic --mks-fg-inverse vs brand-component --mks-mark-stroke) only surfaced once the operator saw the dark theme. This is the inherent cost of operator-empirical aesthetics judgments — and is exactly why D-P4-03 was scoped as autonomous: false.

Task Commits

Each plan task was committed atomically with normal git commits + pre-commit hooks (sequential foreground mode; in-line with Plans 04-01 through 04-05 + 04-08 protocol):

  1. Task 1 — Wave 0 RED — inline-SVG source-contract + cursor-visibility regression pinf0b88d4 (test). 2 files created: tests/welcome/inline-svg.test.ts (3 it() blocks; 70 lines); tests/build/cursor-visibility.test.ts (1 it() block; 45 lines). 3 RED + 1 GREEN-on-arrival.
  2. Task 2 — Wave 1 GREEN — SVG stroke recolor + welcome.ts ?raw import + populateMark inline injection + globals.d.ts ambient declc416143 (feat). 3 files modified: src/shared/brand/mokosh-mark.svg (+1/-1); src/welcome/welcome.ts (+90/-25); globals.d.ts (+21). 3 source-contract RED flipped to 3 GREEN; vitest 184 -> 188 (full suite 187/188 with the 04-CONTEXT #9/#10 webm-remux flake; isolation 5/5 GREEN — tolerated per Task 2 VITEST GATE LOGIC).
  3. Task 3 — A17.8 raw-source update + NEW A35 live-DOM harness assertion + 01-07-SUMMARY back-patch + deferred-items correction3f8e31a (feat). 5 files modified: tests/uat/extension-page-harness.ts (A17.8 narrowed honestly); tests/uat/lib/harness-page-driver.ts (driveA35 +247 lines; before debug-fix strengthening: 4 sub-checks); tests/uat/harness.test.ts (3-site wiring +28); .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (5 lines flipped); .planning/phases/04-harden-clean-up-optional/deferred-items.md (mis-diagnosis correction). UAT 35/35 GREEN (A35 added + 4 sub-checks all PASS pre-debug).
  4. Task 4 artifact — Operator-empirical screenshot harnessd66cbf6 (chore). 1 file created: scripts/04-06-welcome-hero-screenshots.mjs (194 lines). Reproducible Puppeteer script for the operator-empirical checkpoint.
  5. Task 4 operator-empirical UAT — first verdict — TWEAK (no commit; verdict only). Dark cascade flipped the icon to ink-900 indigo on madder — lower contrast than light. Routed via /gsd-debug per feedback-gsd-ceremony-for-fixes.md.
  6. Debug-fix — decouple welcome-hero mark stroke via --mks-mark-strokea8bcc17 (fix). 6 files modified: src/shared/tokens.css (NEW --mks-mark-stroke token in :root; +15); src/welcome/welcome.css (rewire to new token; +11); src/welcome/welcome.ts (comment-only); tests/welcome/inline-svg.test.ts (comment-only); tests/uat/lib/harness-page-driver.ts (A35 strengthening + a35Probe helper extraction + A35.5 light+dark equality decouple-proof); tests/uat/harness.test.ts (comment-only). UAT 36/36 GREEN (A35 now has 5 sub-checks all PASS).
  7. Task 4 operator-empirical UAT — re-verdict — CONFIRMED FIXED (no commit; verdict only). Operator response 2026-05-26: "Confirmed fixed — close Plan 04-06".

Plan metadata commit: to be committed atomically with this SUMMARY + STATE.md + ROADMAP.md + the archived debug session as docs(04-06): complete dark-logo contrast + cursor-visibility verification + D-P4-03 closure (4 tasks; UAT 36/36 GREEN; operator re-confirmed 2026-05-26).

Files Created/Modified

Created

  • tests/welcome/inline-svg.test.tsCREATED (70 lines). 3 it() blocks in describe('UI-SPEC dark-logo currentColor strategy — source-level contract', ...). Node-env file-read + string-assert pattern (precedent: tests/i18n/manifest-i18n.test.ts). Tests: A (mokosh-mark.svg recolor: contains stroke="currentColor" + viewBox="0 0 32 32"; does NOT contain stroke="#181b2a"); B (welcome.ts ?raw + DOMParser + replaceChildren; does NOT contain ?url or innerHTML); C (globals.d.ts ambient *.svg?raw decl). NO DOM library import; NO @vitest-environment jsdom directive (verified by grep -E "^import .*jsdom|@vitest-environment jsdom" returns 0).
  • tests/build/cursor-visibility.test.tsCREATED (45 lines). 1 it() block; node-env file-grep regression pin for cursor: 'always' literal at src/offscreen/recorder.ts:285 (shipped opportunistically by Plan 01-09).
  • scripts/04-06-welcome-hero-screenshots.mjsCREATED (194 lines). Puppeteer script; loads dist/ as unpacked extension; opens chrome-extension://${extensionId}/src/welcome/welcome.html; captures /tmp/04-06-welcome-hero-light.png + /tmp/04-06-welcome-hero-dark.png (dark via Emulation.setEmulatedMedia prefers-color-scheme: dark); prints diagnostic getComputedStyle().stroke for each theme.
  • .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.mdCREATED (this file).

Modified

  • src/shared/brand/mokosh-mark.svgMODIFIED. Net +1 / -1. Root <svg> (line 2) stroke="#181b2a" -> stroke="currentColor". 12 <line> + 1 <rect> children inherit stroke from the root and are UNCHANGED.
  • src/welcome/welcome.tsMODIFIED. Net +90 / -25. Line 46 import flipped from ?url/markUrl to ?raw/markSvg. populateMark body (lines 159-179) rewritten: <img> -> DOMParser + replaceChildren inline-SVG. NEVER uses innerHTML. Preserves function signature, slots query, altText resolution, filter-pipeline form, empty-slot logger.warn fallback. Docstring + comment block updated to describe inline-<svg> strategy.
  • globals.d.tsMODIFIED. Net +21. Appended declare module '*.svg?raw' { const raw: string; export default raw; } block after the existing *.svg?url block. Explanatory comment per project docstring convention.
  • src/shared/tokens.cssMODIFIED. Net +15. Added --mks-mark-stroke: var(--mks-linen-50); in the universal :root block (line ~131), adjacent to --mks-fg-inverse. NOT added to .dark, [data-theme="dark"] block. Comment cites the abstraction-error rationale + .planning/debug/04-06-dark-mode-mark-decouple.md. SCOPE EXPANSION (authorized by Task 4 TWEAK verdict).
  • src/welcome/welcome.cssMODIFIED. Net +11. Line 72 .welcome-hero__mark { color: var(--mks-fg-inverse); } -> color: var(--mks-mark-stroke);. Comment block updated to cite the cascade chain. SCOPE EXPANSION (authorized by Task 4 TWEAK verdict).
  • tests/uat/extension-page-harness.tsMODIFIED. Net +25 / -45. A17.8 sub-check (lines ~2249-2298) narrowed honestly: replaced hasInlineDataUrl + svgFileUrl regex with raw-source grep (stroke="currentColor" + viewBox="0 0 32 32"). Comment block disavows live-DOM coverage and points to A35.
  • tests/uat/lib/harness-page-driver.tsMODIFIED. Net +247 (Task 3 first land) + strengthening (debug-fix; A35.5 sub-check + a35Probe helper extraction). Appended driveA35 host-side driver after driveA34. Opens welcome.html via browser.newPage() + page.goto(...) + waitForSelector('.welcome-hero__mark svg', ...). Extracted a35Probe(welcomePage, dark) helper toggles data-theme="dark" on documentElement + reads live DOM. 5 CheckRecords: A35.1-A35.4 (live-DOM injection + currentColor cascade); A35.5 (light+dark equality decouple-proof). welcomePage.close() in finally; try/catch + result.error mirror driveA33/A34.
  • tests/uat/harness.test.tsMODIFIED. Net +28. 3-site lockstep wiring: import block adds driveA35,; wrapped-driver block adds driveA35Wrapped closure (capturing handles.browser + handles.extensionId); drivers-array push appends { name: 'A35', drive: driveA35Wrapped } as the LAST entry. Architecture banner string at line 283 LEFT unchanged (cosmetic-advisory ADV-2A; auto-count via total = drivers.length + 1 carries actual count).
  • .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.mdMODIFIED. Net +10 / -10. 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09 at recorder.ts:285; verified Phase 4 Plan 04-06'. 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged. After the flips the SUMMARY narrative is internally consistent.
  • .planning/phases/04-harden-clean-up-optional/deferred-items.mdMODIFIED. Net +2 / -2. The prior 'strict-meta-json fails on a clean tree' entry rewritten to describe the real 04-CONTEXT #9/#10 parallel-vitest ffprobe flake family + correct the baseline to 184/184 GREEN.
  • .planning/ROADMAP.mdMODIFIED. Plan 04-06 row flipped [ ] -> [x] with closure annotation. Phase 4 progress table cell updated from 6/8 In Progress (Plan 04-05 closed ROADMAP SC #2) to 7/8 In Progress (Plan 04-06 closed D-P4-03).
  • .planning/STATE.mdMODIFIED. Current Position advanced. Stopped At updated. Last activity 2026-05-26. Plan 04-06 closure section added. Decisions appended. Performance metric row added. Progress 90% -> 93% (28/31 -> 29/31).

Archived

  • .planning/debug/04-06-dark-mode-mark-decouple.md -> .planning/debug/resolved/04-06-dark-mode-mark-decouple.md — debug session moved at plan closure (resolved end-to-end via fix commit a8bcc17 + operator re-empirical 2026-05-26).

Decisions Made

See key-decisions in frontmatter for the canonical list (10 decisions). Highlights:

  1. Multi-iteration ceremony was a necessary cost — 3 planner passes + 2 checker passes + 1 debug fix cycle. The /gsd-debug fix surfaced the brand-component vs semantic token abstraction error that the planner-checker couldn't catch (requires operator visual judgment). Lesson encoded.
  2. Brand-component token vs semantic token--mks-fg-inverse is for theme-flipping text foreground; --mks-mark-stroke is for theme-independent brand stroke. Pattern established and documented.
  3. A35.5 decouple-proof sub-check — light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)'. Regression-proof for any future re-coupling.
  4. Honest A17.8 narrowing — SOURCE-BUNDLING only; live-DOM coverage lives in A35.
  5. Behavior-based vitest gate — hard-codes no test filename; isolation-passing single flake is tolerated; reproducible failure or 2+ failures FAIL the gate.
  6. 01-07-SUMMARY back-patch — 5 stale framing lines flipped; 4 historical commit-description lines left.
  7. Cosmetic advisories ADV-2A/B/C — banner string left for Plan 04-07 to opportunistically refresh; SKIP_PROD_REBUILD=0 prose accuracy noted; A35-appended-LAST safety documented in SUMMARY.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 — Blocking] Welcome screenshot script created from scratch (Task 4 artifact)

  • Found during: Task 4 (Operator empirical UAT — pre-checkpoint screenshot production)
  • Issue: The plan instructed "Load dist/ as an unpacked extension and open the welcome page in a Puppeteer-driven Chrome. Capture LIGHT-surface screenshot. Capture DARK-surface screenshot." but did not provide a dedicated script — the operator-empirical checkpoint required a reproducible artifact.
  • Fix: Created scripts/04-06-welcome-hero-screenshots.mjs (194 lines) — Puppeteer; loads dist/ as unpacked extension; opens welcome.html; captures /tmp/04-06-welcome-hero-{light,dark}.png via Emulation.setEmulatedMedia prefers-color-scheme: dark; prints diagnostic getComputedStyle().stroke for each theme. Reproducible for any future re-shoot.
  • Files modified: scripts/04-06-welcome-hero-screenshots.mjs (CREATED)
  • Verification: Both screenshots produced + visually inspected by operator; diagnostic output confirms computedStroke resolved for each theme.
  • Committed in: d66cbf6 (separate chore commit; Task 4 artifact)

2. [Rule 1 — Bug] Dark-cascade abstraction error: --mks-fg-inverse couples stroke to theme

  • Found during: Task 4 operator-empirical UAT (TWEAK verdict 2026-05-26)
  • Issue: .welcome-hero__mark { color: var(--mks-fg-inverse); } is theme-coupled (linen-50 in light; ink-900 in dark per tokens.css :root vs .dark blocks). The mark sits on a theme-INDEPENDENT madder-600 circle, so a theme-coupled stroke produced muddy ink-on-madder in dark mode. Wrong abstraction (semantic token applied to a non-flipping surface).
  • Fix: Introduced a NEW brand-component token --mks-mark-stroke = var(--mks-linen-50) in the universal :root block of src/shared/tokens.css. CRUCIALLY NOT overridden in .dark, [data-theme="dark"] — stays linen-50 on every surface. Rewired .welcome-hero__mark { color: var(--mks-mark-stroke); } at src/welcome/welcome.css line 72. SVG remains untouched (stroke="currentColor" cascade plumbing identical; only the wrapper's color source changed). Routed via /gsd-debug per feedback-gsd-ceremony-for-fixes.md — no inline hot-edit.
  • Files modified: src/shared/tokens.css (SCOPE EXPANSION; +15); src/welcome/welcome.css (SCOPE EXPANSION; +11); src/welcome/welcome.ts (comment-only); tests/welcome/inline-svg.test.ts (comment-only); tests/uat/lib/harness-page-driver.ts (A35 strengthening: a35Probe helper + A35.5 light+dark equality); tests/uat/harness.test.ts (comment-only); .planning/debug/04-06-dark-mode-mark-decouple.md (NEW debug note; later archived to resolved/).
  • Verification: Re-shot screenshots show identical crisp linen-on-madder in both themes. A35.5 live-DOM probe: light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)". UAT 36/36 GREEN. Operator re-empirical 2026-05-26: "Confirmed fixed — close Plan 04-06".
  • Committed in: a8bcc17 (debug-fix commit; ceremony-routed per saved memory)

3. [Rule 1 — Bug] A35 sub-check strengthening: 4 -> 5 sub-checks (A35.5 decouple-proof added)

  • Found during: /gsd-debug session for the dark-cascade abstraction error
  • Issue: The original A35 (Task 3 first land) had 4 sub-checks (A35.1-A35.4) covering live-DOM injection + currentColor cascade resolution in LIGHT theme only. A future regression that re-couples the stroke to a theme-flipping token would still pass A35.1-A35.4 (the inline <svg> is injected; the cascade resolves; just to the wrong color in dark).
  • Fix: Extracted the live-DOM probe into a helper a35Probe(welcomePage, dark) that toggles data-theme="dark" on documentElement (+ requestAnimationFrame wait for CSS recompute). Probe BOTH light + dark. Add A35.5: assert light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)" (linen-50). This is the canonical decouple-proof.
  • Files modified: tests/uat/lib/harness-page-driver.ts (folded into debug-fix commit a8bcc17)
  • Verification: UAT 36/36 GREEN; A35.5 diagnostics show light + dark both rgb(250, 247, 241).
  • Committed in: a8bcc17 (folded into the debug-fix commit; ceremony-routed)

Total deviations: 3 auto-fixed (1 blocking [screenshot script]; 2 bugs [abstraction error + A35 strengthening]). Impact on plan: All 3 deviations were essential for closure. The screenshot script was the operator-empirical artifact; the abstraction-error fix closed the TWEAK verdict; the A35 strengthening encodes the regression-proof. No scope creep — the SCOPE EXPANSIONS (tokens.css + welcome.css edits) were authorized by the Task 4 TWEAK verdict per the operator-empirical resume-signal contract.

Issues Encountered

Operator-empirical TWEAK verdict — abstraction error in dark cascade

Task 4's first operator-empirical verdict (2026-05-26) was TWEAK NEEDED. The light screenshot was crisp linen-on-madder (Plan 01-10 baseline preserved); the dark screenshot showed the mark stroke flipped to ink-900 (deep indigo) on the madder-600 wrapper — visually muddy, low contrast. Root cause: .welcome-hero__mark { color: var(--mks-fg-inverse); } couples the cascade to a semantic token that flips with theme. The mark wrapper is theme-independent (madder-600 in both themes), so the coupling was the wrong abstraction.

Resolution (per saved memory feedback-gsd-ceremony-for-fixes.md): routed via /gsd-debug rather than inline hot-edit. Debug session at .planning/debug/04-06-dark-mode-mark-decouple.md established root cause + fix; fix commit a8bcc17 decoupled via the NEW brand-component token --mks-mark-stroke. A35 strengthened with the A35.5 decouple-proof sub-check. Re-shot screenshots + UAT re-run produced identical crisp linen-on-madder in both themes. Operator re-empirical 2026-05-26: "Confirmed fixed — close Plan 04-06".

Parallel-vitest #9/#10 flake fired during Task 2 GREEN gate

Task 2's full vitest run produced 187 passed / 1 failed (188 total). The failing test was tests/background/webm-remux.test.ts > 'ffprobe -count_frames reports between 905 and 912 frames' (Error: Test timed out in 5000ms). Per the Task 2 VITEST GATE LOGIC behavior-based rule: re-run in isolation (npm test -- tests/background/webm-remux.test.ts --run) -> 5/5 GREEN. Tolerated as the known 04-CONTEXT #9/#10 parallel-vitest ffprobe-timeout flake family. NOT a Plan 04-06 regression (Plan 04-06 modifies no offscreen recorder code). Documented + tolerated per gate logic.

Pre-Checkpoint Bundle Gates (6/6 PASS)

Per saved memory feedback-pre-checkpoint-bundle-gates.md, all 6 gates run on npm run build production output before each operator-empirical checkpoint surface:

Gate Check Result
1 npm run build exit 0; glob-existence ls dist/assets/index.ts-*.js PASS
2 SW CSP-safety: new Function/eval in dist/assets/index.ts-*.js 0 / 0 — PASS (Plan 04-02 effect preserved)
3 SW Node-globals: Buffer./process./require( in SW chunk 1 (pre-existing JSZip polyfill typeof ArrayBuffer<"u" feature-detection) — PASS (baseline-identical to Plan 04-05 boundary; Plan 04-06 modifies no SW chunk source)
4 DOM-globals: window./document. in SW chunk third-party typeof window<"u"/typeof document<"u" guarded feature-detection — PASS (baseline-identical)
5 Tier-1 SW-bundle-import vitest gate GREEN
6 Tier-1 FORBIDDEN_HOOK_STRINGS at 12 + Tier-2 production filename-leak gate (0 hits) GREEN — PASS

6/6 PASS at both the first Task 4 checkpoint AND the post-debug re-checkpoint. Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 (Plan 04-06 adds no __MOKOSH_UAT__-gated symbols; DOMParser is a standard web platform API; ?raw is a normal production Vite import; *.svg?raw ambient decl is type-only at build time). SW chunk is byte-identical at the Plan 04-05 boundary (Plan 04-06 modifies only src/welcome/* + src/shared/brand/mokosh-mark.svg + src/shared/tokens.css :root token + tests/* + globals.d.ts — none of these affect the SW chunk shape).

Test Baselines

  • vitest: 188/188 GREEN on a fully-clean run (the +4 from Plan 04-06: 3 inline-svg.test.ts + 1 cursor-visibility.test.ts). Most recent full-suite run post-debug-fix: 187/188 with the 04-CONTEXT #9/#10 webm-remux flake fired (passes 5/5 in isolation; tolerated per Task 2 VITEST GATE LOGIC). Baseline contract held: 184 + 4 = 188.
  • UAT harness: 36/36 GREEN (skip-mode SKIP_LONG_UAT=1 npm run test:uat). Increments: 33 (post-04-03) -> 34 (04-08 A33) -> 35 (04-05 A34) -> 36 (04-06 A35). A35 itself contains 5 sub-checks all PASS (A35.1-A35.5).
  • Tier-1 FORBIDDEN_HOOK_STRINGS: 12 (unchanged from Plan 04-02 baseline; verified at tests/background/no-test-hooks-in-prod-bundle.test.ts:108-126).
  • Tier-2 production-bundle filename-leak gate: synthetic-display-source = 0 hits in dist/ (Plan 04-08 invariant preserved).
  • Pre-checkpoint bundle gates: 6/6 PASS (verified at both Task 4 checkpoint AND post-debug re-checkpoint).
  • tsc --noEmit: exits 0. npm run build: exits 0. npm run build:test: exits 0.

D-P4-03 Closure Evidence

D-P4-03 (charter, locked, 04-CONTEXT.md): BOTH (a) cursor visibility AND (b) dark-surface logo contrast.

(a) Cursor visibility — CLOSED

  • Already shipped: cursor: 'always' at src/offscreen/recorder.ts:285 (Plan 01-09 opportunistic co-land in commit a2dfc8c 2026-05-19; the original Phase 5 deferral framing in 01-07-SUMMARY was false — the constraint was shipped concurrently with the no-active-tab cleanup).
  • Verified by: tests/build/cursor-visibility.test.ts — node-env file-grep regression pin for the literal cursor: 'always'. 1 GREEN-on-arrival. Any future regression that removes the literal will fail the gate.
  • 01-07-SUMMARY back-patch: 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped; 4 historical commit-description lines (40, 89, 109, 110) LEFT.

(b) Dark-surface logo contrast — CLOSED

4-layer coverage:

  1. Source-contract unit tests (Task 1): tests/welcome/inline-svg.test.ts (3 tests; node-env; file-read + string-assert). Pins SVG recolor + welcome.ts ?raw/DOMParser/replaceChildren/no-innerHTML + globals.d.ts ambient decl. RED -> GREEN after Task 2.
  2. A17.8 raw-source bundling check (Task 3): tests/uat/extension-page-harness.ts. Asserts welcome JS chunk contains the raw SVG source signature (stroke="currentColor" + viewBox="0 0 32 32"). Honestly narrowed to SOURCE-BUNDLING only; explanatory comment disavows live-DOM coverage and points to A35.
  3. A35 live-DOM host-side harness assertion (Task 3 + debug-fix strengthening): tests/uat/lib/harness-page-driver.ts driveA35. Opens welcome.html as a real Puppeteer tab; lets populateMark() run at DOMContentLoaded; reads the LIVE injected .welcome-hero__mark svg element + getComputedStyle().stroke. 5 sub-checks: A35.1 svg present; A35.2 stroke="currentColor"; A35.3 getComputedStyle().stroke resolved non-default (linen-50); A35.4 no <img> in slot; A35.5 light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)" (linen-50 decouple-proof, NEW from debug-fix). UAT 36/36 GREEN — A35 is the genuine automated proof of the runtime injection + cascade + theme decoupling.
  4. Operator-empirical Puppeteer screenshots (Task 4): scripts/04-06-welcome-hero-screenshots.mjs produces /tmp/04-06-welcome-hero-{light,dark}.png via prefers-color-scheme: dark emulation. First operator verdict: TWEAK (dark cascade flipped to ink-900). /gsd-debug session resolved via --mks-mark-stroke brand-component token. Re-shot screenshots + operator re-empirical 2026-05-26: "Confirmed fixed — close Plan 04-06".

TDD Gate Compliance

Plan 04-06 was a type: execute plan with tdd="true" on Tasks 1-2 (the source-contract + implementation pair). Gate sequence verified in git log:

  1. RED gate (test commit): f0b88d4 (test(04-06): Wave 0 — inline-SVG source-contract RED + cursor-visibility regression pin). 3 RED + 1 GREEN-on-arrival at commit time; isolated npm test -- tests/welcome/ --run showed 3/3 FAILED before Task 2 GREEN.
  2. GREEN gate (feat commit): c416143 (feat(04-06): Wave 1 GREEN — dark-logo currentColor strategy + inline-SVG injection) after RED. 3 RED flipped to 3 GREEN; cursor-visibility test preserved 1 GREEN.
  3. REFACTOR gate: N/A — no separate refactor commit needed; Task 2 implementation was minimal-to-pass.

TDD discipline preserved end-to-end.

Charter Linkage

Plan 04-06 has no REQ-* requirements (Phase 4 is optional hardening; all v1 REQs covered by Phases 1-3 per ROADMAP). Charter linkage is via the charter-d-p4-03 tag in frontmatter. D-P4-03 = visual polish (cursor visibility + dark-surface logo contrast) — both items closed by Plan 04-06.

Cross-Plan Provenance Note for Plan 04-07

Plan 04-07 (Phase 4 closure aggregator) is now the ONLY remaining Phase 4 plan. Inputs for 04-07:

  • D-P4-03 closure: documented here (cursor + dark-logo both closed; A35 + cursor-visibility unit test pin live).
  • D-P4-04 (alpha tester integration): honored out-of-band (per ROADMAP entry).
  • ROADMAP success criteria status: SC #1 CLOSED (04-08); SC #2 CLOSED (04-05); SC #3 CLOSED (04-02); SC #4 CLOSED (04-02). All 4 ROADMAP success criteria are now CLOSED.
  • Cosmetic-advisory ADV-2A (architecture banner string at harness.test.ts:283 missing A33/A34/A35): non-blocking; Plan 04-07 may opportunistically refresh the banner if it touches that file.
  • 04-CONTEXT #9/#10 parallel-vitest flake family: documented in deferred-items.md (corrected this plan); routed for /gsd-debug or future-plan worker-isolation strategy (poolOptions.threads.singleThread:true or testTimeout bump) if it becomes a blocker for Phase 4 closure.

Plan 04-07 should produce the Phase 4 aggregate verification (VERIFICATION.md gsd-verifier audit) + REQUIREMENTS/ROADMAP/STATE marker flips + v1 milestone close prep.

Saved-Memory Compliance Audit

  • feedback-no-unilateral-scope-reduction.md — full plan scope executed (4 tasks; 11 files modified; SCOPE EXPANSIONS authorized by operator TWEAK verdict; no unilateral truncation).
  • feedback-trust-harness-over-manual-uat.md — operator only judged from screenshots (Task 4 artifact via scripts/04-06-welcome-hero-screenshots.mjs); A35.5 light+dark equality decouple-proof landed in the automated harness (not just the screenshot artifact).
  • feedback-gsd-ceremony-for-fixes.md — the dark-cascade abstraction error was routed via /gsd-debug (debug session at .planning/debug/04-06-dark-mode-mark-decouple.md; fix commit a8bcc17 is a separate atomic fix-commit, NOT a hot-edit folded into a task commit). Ceremony honored.
  • feedback-pre-checkpoint-bundle-gates.md — 6/6 bundle gates run + verified PASS at BOTH the first Task 4 checkpoint AND the post-debug re-checkpoint. Documented above.

Threat Surface Scan

Plan 04-06 introduces no new security-relevant surface:

  • Inline-SVG injection: safe because the input is a Vite-bundled compile-time literal (no runtime untrusted input). DOMParser parse + replaceChildren preserves MV3 CSP discipline (T-04-06-01 mitigation; no innerHTML, no eval). The ?raw import is processed at build time; the SVG source string is statically known.
  • NEW A35 harness tab: test-only; always closed in finally block (no production surface, no leaked tab); A35 is appended LAST in the drivers array (no pollution-of-future-drivers risk — there are no future drivers); A35 does NOT mutate chrome.storage (opening welcome.html does NOT trigger openWelcomeIfFirstInstall — that runs only on chrome.runtime.onInstalled). Verified safe at design time (cosmetic-advisory ADV-2C).
  • --mks-mark-stroke brand-component token: purely additive CSS custom property in :root; no override anywhere; no consumer surface change.

No ## Threat Flags section needed.

Self-Check: PASSED

Acceptance criterion by criterion (cross-referenced against the success_criteria list in the closure-work prompt):

  • 04-06-SUMMARY.md written per canonical template + multi-iteration ceremony history: this file. Frontmatter complete (phase + plan + subsystem + tags + requires + provides + affects + tech-stack + key-files + key-decisions + metrics). Multi-Iteration Ceremony History section provides the 13-row provenance table.
  • All 4 tasks documented: Tasks 1-3 autonomous (commits f0b88d4 + c416143 + 3f8e31a) + Task 4 operator empirical with TWEAK → fix a8bcc17 → CONFIRMED arc.
  • All key deliverables enumerated: currentColor + --mks-mark-stroke decoupling + A35 (5 sub-checks) + back-patch + screenshot script + 01-07-SUMMARY 5-line flip + deferred-items correction.
  • 3 re-plan-checker iter-2 cosmetic advisories documented: ADV-2A (banner: LEAVE per decision), ADV-2B (SKIP_PROD_REBUILD=0 rationale corrected in SUMMARY), ADV-2C (A35-appended-LAST safety noted in Threat Surface Scan).
  • Pre-checkpoint bundle gates 6/6 PASS reported: dedicated section above with table.
  • Test baselines documented: vitest 188/188 GREEN (most recent 187/188 with tolerated webm-remux flake); UAT 36/36 GREEN; FORBIDDEN_HOOK_STRINGS 12.
  • D-P4-03 closure evidence: dedicated section with (a) cursor visibility + (b) dark-surface logo contrast 4-layer coverage.
  • Self-Check section: this section, criterion by criterion.

Created files exist:

  • tests/welcome/inline-svg.test.ts — FOUND
  • tests/build/cursor-visibility.test.ts — FOUND
  • scripts/04-06-welcome-hero-screenshots.mjs — FOUND
  • .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md — FOUND (this file)

Commits exist (verified via git log --oneline):

  • f0b88d4 (Task 1 RED) — FOUND
  • c416143 (Task 2 GREEN) — FOUND
  • 3f8e31a (Task 3 A35 + back-patch) — FOUND
  • d66cbf6 (Task 4 screenshot harness) — FOUND
  • a8bcc17 (debug-fix --mks-mark-stroke) — FOUND

No missing items. Plan 04-06 closes cleanly.


Phase: 04-harden-clean-up-optional Plan: 06 Completed: 2026-05-26