Files
mokosh/.planning/debug/resolved/04-06-dark-mode-mark-decouple.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

11 KiB

status, trigger, created, updated
status trigger created updated
awaiting_human_verify 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 2026-05-26T00:00:00Z 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 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)