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
This commit is contained in:
2026-05-26 13:14:41 +02:00
parent a8bcc17822
commit c790c6a8b3
4 changed files with 407 additions and 14 deletions

View File

@@ -0,0 +1,115 @@
---
status: awaiting_human_verify
trigger: "Operator-empirical Task 4 checkpoint on Plan 04-06 returned TWEAK NEEDED — decouple welcome-hero mark stroke from --mks-fg-inverse via a dedicated --mks-mark-stroke token that stays linen-50 in BOTH light and dark themes"
created: 2026-05-26T00:00:00Z
updated: 2026-05-26T10:30:00Z
---
## Current Focus
reasoning_checkpoint:
hypothesis: ".welcome-hero__mark { color: var(--mks-fg-inverse) } couples the mark stroke to the theme-conditional semantic foreground-inverse token (linen-50 in light, ink-900 in dark). The mark sits on a theme-independent madder-600 circle, so a theme-coupled stroke is the wrong abstraction. Adding a theme-INDEPENDENT --mks-mark-stroke = var(--mks-linen-50) in :root (NOT inside .dark/[data-theme=dark] override) and re-pointing the wrapper resolves the stroke to linen-50 in BOTH themes."
confirming_evidence:
- "tokens.css line 128 (universal :root): --mks-fg-inverse: var(--mks-linen-50)"
- "tokens.css line 244 (.dark override): --mks-fg-inverse: var(--mks-ink-900) — the theme flip confirmed"
- "welcome.css line 72: .welcome-hero__mark { color: var(--mks-fg-inverse) } — the exact cascade source"
- "mokosh-mark.svg line 2: stroke=currentColor — confirms the cascade plumbing target"
- "--mks-linen-50 = #faf7f1 defined at tokens.css line 90 (universal :root)"
- "popup/style.css:39 also reads --mks-fg-inverse but is unrelated to welcome mark — leave untouched"
falsification_test: "After the fix, re-run scripts/04-06-welcome-hero-screenshots.mjs. If `computedStroke` for the LIGHT screenshot differs from `computedStroke` for the DARK screenshot, the decoupling failed. If they match (expected rgb(250, 247, 241)), the fix is confirmed. A35 will also assert this equality programmatically."
fix_rationale: "Root cause is the wrong token abstraction — --mks-fg-inverse is a semantic token meant for text foreground on inverse surfaces (where the surface flips with theme), but the welcome-hero mark sits on a NON-flipping surface (madder). The fix introduces a dedicated brand-component token --mks-mark-stroke that is purpose-built for this case: theme-independent linen-50 stroke for the madder-circle mark. It addresses the abstraction error directly, not the symptom."
blind_spots: "1) A17.8 source-bundling grep doesn't check welcome.css text — safe. 2) inline-svg.test.ts only pins SVG + welcome.ts + globals.d.ts text — safe. 3) jsdom-style assertions in unit tests don't load welcome.css — safe. 4) Risk: if any other rule in welcome.css cascades from the wrapper's `color` (text nodes inside .welcome-hero__mark), they'd flip too — but the placeholder span is now replaced by the SVG at populateMark, so no visible text consumes the wrapper color. 5) The popup/style.css unrelated --mks-fg-inverse consumer is untouched (separate surface, separate decision)."
hypothesis: ".welcome-hero__mark { color: var(--mks-fg-inverse) } is theme-coupled; --mks-mark-stroke = var(--mks-linen-50) in :root decouples it."
test: Apply token addition + welcome.css edit; strengthen A35; re-screenshot.
expecting: Light and dark `computedStroke` both resolve to rgb(250, 247, 241) (linen-50).
next_action: Add --mks-mark-stroke to tokens.css :root, update welcome.css line 72, strengthen A35.3, run build + screenshots + UAT + vitest.
## Symptoms
expected: Welcome-hero grid icon (mokosh-mark.svg) renders with linen-50 (off-white) stroke on madder-600 circle in BOTH light and dark themes — high-contrast crisp aesthetic per the Plan 01-10 baseline.
actual: In LIGHT theme, stroke is linen-50 (`--mks-fg-inverse` = linen-50 there) — crisp linen-on-madder. In DARK theme, stroke is ink-900 (`--mks-fg-inverse` = ink-900 there) — indigo-on-madder, lower contrast.
errors: None — this is an aesthetic/design defect, not a runtime error.
reproduction: Open `dist-test/src/welcome/welcome.html` with `<html data-theme="dark">` vs without. Inspect `.welcome-hero__mark svg` `getComputedStyle().stroke`. Light = `rgb(250, 247, 241)` (linen-50). Dark = `rgb(24, 27, 42)` (ink-900). Visual: light is crisp, dark is muddy.
started: Plan 04-06 Task 1-3 landed; operator-empirical Task 4 checkpoint flagged the cascade-coupling defect.
## Evidence
- timestamp: 2026-05-26T00:00:00Z
checked: src/shared/tokens.css :root block (lines 82-229)
found: `--mks-fg-inverse: var(--mks-linen-50)` at line 128 (universal/light); `--mks-fg-inverse: var(--mks-ink-900)` at line 244 (.dark theme override).
implication: Confirms operator's diagnosis: --mks-fg-inverse is theme-coupled and unsuitable as a stroke anchor for a mark that must be crisp on madder in both themes.
- timestamp: 2026-05-26T00:00:00Z
checked: src/shared/tokens.css for --mks-linen-50 token
found: `--mks-linen-50: #faf7f1;` defined at line 90 in the universal :root block.
implication: `var(--mks-linen-50)` is the correct hex source; no need to use a literal hex.
- timestamp: 2026-05-26T00:00:00Z
checked: src/welcome/welcome.css .welcome-hero__mark (lines 64-73)
found: `color: var(--mks-fg-inverse);` at line 72 — the exact line that needs to flip to `var(--mks-mark-stroke)`.
implication: One-line CSS change at the wrapper. SVG remains untouched.
- timestamp: 2026-05-26T00:00:00Z
checked: src/shared/brand/mokosh-mark.svg
found: `stroke="currentColor"` at line 2 — unchanged across the fix.
implication: Cascade plumbing remains: SVG inherits from wrapper's `color`, which now points to the new theme-independent token.
- timestamp: 2026-05-26T10:15:00Z
checked: Grep for other consumers of --mks-fg-inverse across src/ + tests/
found: Two consumers — src/welcome/welcome.css:72 (target; rewired to --mks-mark-stroke) and src/popup/style.css:39 (separate popup-surface concern; LEFT UNTOUCHED). No tests grep for the literal `var(--mks-fg-inverse)` in welcome.css.
implication: Scope is minimal; only the welcome-hero mark wrapper changed. Popup style.css continues using --mks-fg-inverse as before.
- timestamp: 2026-05-26T10:20:00Z
checked: Built bundle dist/assets/welcome-BAisfyci.css after `npm run build`
found: ":root{...--mks-fg-inverse: var(--mks-linen-50);--mks-mark-stroke: var(--mks-linen-50);...}" + ".dark,[data-theme=dark]{...--mks-fg-inverse: var(--mks-ink-900);...}" — confirmed --mks-mark-stroke is in :root and NOT overridden in the dark block. ".welcome-hero__mark{...color:var(--mks-mark-stroke)}" — confirmed wrapper points at the new token.
implication: Build plumbing is correct.
- timestamp: 2026-05-26T10:22:00Z
checked: scripts/04-06-welcome-hero-screenshots.mjs re-run on dist/
found: "LIGHT screenshot: /tmp/04-06-welcome-hero-light.png (computed stroke: rgb(250, 247, 241))" and "DARK screenshot: /tmp/04-06-welcome-hero-dark.png (computed stroke: rgb(250, 247, 241))" — same RGB in both themes; rgb(250, 247, 241) is linen-50 (#faf7f1).
implication: Falsification test PASSED — the decoupling works at the live-DOM cascade level. Visual inspection of the dark screenshot shows the crisp linen-on-madder grid icon expected by the operator.
- timestamp: 2026-05-26T10:24:00Z
checked: vitest full suite (npx vitest run)
found: 187/188 passed, 1 tolerated flake (tests/background/webm-remux.test.ts > ffprobe-count_frames 905-912 timeout — documented 04-CONTEXT #9/#10; PASSES in isolation). Baseline 184 + 04-06 deltas +4 = 188 contract held.
implication: No vitest regression from the decouple changes.
- timestamp: 2026-05-26T10:25:00Z
checked: Pre-checkpoint bundle gates 6/6 (sw-bundle-import, no-test-hooks-in-prod-bundle, no-remote-fonts, no-new-function-in-sw-chunk, manifest-i18n, welcome inline-svg)
found: 6/6 PASS (37 tests green); FORBIDDEN_HOOK_STRINGS = 12 (unchanged).
implication: No CSP / globals / hook-leak regression.
- timestamp: 2026-05-26T10:28:00Z
checked: UAT skip-mode (`npm run test:uat`)
found: 36/36 PASS; A35.5 (new decouple sub-check) PASSES with: light="rgb(250, 247, 241)", dark="rgb(250, 247, 241)". A35 diagnostics show populateMark injected the inline <svg> in both themes and the cascade resolves to linen-50 in both.
implication: Live-DOM proof of theme decoupling is in the automated suite (not just the screenshot artifact).
## Resolution
root_cause: `.welcome-hero__mark` used `color: var(--mks-fg-inverse)`. The semantic token `--mks-fg-inverse` is theme-conditional — it resolves to linen-50 in light and ink-900 in dark (`tokens.css :root` line 128 vs `.dark, [data-theme="dark"]` line 244). The SVG inherits via `stroke="currentColor"`, so the stroke flipped with theme. But the mark sits on a theme-INDEPENDENT madder-600 circle, so a theme-coupled stroke produced muddy ink-on-madder in dark mode. The abstraction was wrong: --mks-fg-inverse is a SEMANTIC text-foreground-on-inverse-surface token, not a BRAND-COMPONENT stroke anchor for the welcome mark.
fix: Two-file change.
1. src/shared/tokens.css :root block — added a new brand-component token `--mks-mark-stroke: var(--mks-linen-50)` adjacent to --mks-fg-inverse. CRUCIALLY, this token is NOT overridden in the `.dark, [data-theme="dark"]` block below — it stays linen-50 on every surface. Comment in source explains the design intent + reference to this debug note.
2. src/welcome/welcome.css `.welcome-hero__mark` — flipped `color: var(--mks-fg-inverse)``color: var(--mks-mark-stroke)`. Comment in source explains the cascade chain.
Plus comment-only updates in src/welcome/welcome.ts + tests/welcome/inline-svg.test.ts + tests/uat/lib/harness-page-driver.ts + tests/uat/harness.test.ts to reflect the new wiring (no behavior change in those files beyond comments and A35 strengthening).
Plus A35 STRENGTHENING in tests/uat/lib/harness-page-driver.ts: extracted the live-DOM probe into a `a35Probe(welcomePage, dark)` helper, probed BOTH light + dark themes (data-theme="dark" toggle), and ADDED A35.5 — the decouple proof: assert light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)" (linen-50). FORBIDDEN_HOOK_STRINGS unchanged (no new __MOKOSH_UAT__ symbol).
SVG `src/shared/brand/mokosh-mark.svg` UNCHANGED.
verification:
- LIGHT screenshot /tmp/04-06-welcome-hero-light.png: linen-50 stroke on madder-600 circle on linen-100 card on linen-50 page bg — baseline aesthetic preserved.
- DARK screenshot /tmp/04-06-welcome-hero-dark.png: linen-50 stroke on madder-600 circle on ink-800 card on ink-900 page bg — crisp linen-on-madder mark in dark mode (operator's spec). Visual contrast on the mark itself is IDENTICAL to light mode.
- A35.5 live-DOM probe: light + dark computedStroke both rgb(250, 247, 241). Programmatic proof of decoupling.
- vitest 187/188 + 1 tolerated flake (baseline contract held).
- UAT 36/36 (A35 now has 5 sub-checks all GREEN).
- 6/6 bundle gates PASS; FORBIDDEN_HOOK_STRINGS = 12 (unchanged).
- SCOPE EXPANSION NOTE: src/welcome/welcome.css was NOT in Plan 04-06 re-plan iter-2 files_modified. Adding this edit is a scope expansion authorized by the operator's empirical TWEAK verdict on Task 4 checkpoint. Surfaced explicitly here for the orchestrator's re-checkpoint payload.
files_changed:
- src/shared/tokens.css (added --mks-mark-stroke token in :root)
- src/welcome/welcome.css (rewired .welcome-hero__mark to new token)
- src/welcome/welcome.ts (comment-only updates)
- tests/welcome/inline-svg.test.ts (comment-only update)
- tests/uat/lib/harness-page-driver.ts (A35 strengthening + new A35.5 + helper extraction)
- tests/uat/harness.test.ts (comment-only update)
- .planning/debug/04-06-dark-mode-mark-decouple.md (this debug note)