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:6a989e8mis-diagnosed strict-meta-json deferred-items entryb59bd24re-plan iter-1 — correct false jsdom premise + back-patch linesdeb68dfre-plan-checker iter-1 — ITERATE-NEEDED (2 BLOCKER)f3baa3are-plan iter-2 — real A35 + corrected 184/184 baseline48c7053re-plan-checker iter-2 — PASSED (0B + 0W + 3 cosmetic-advisories)f0b88d4Task 1 — Wave 0 RED inline-SVG source-contract + cursor pinc416143Task 2 — Wave 1 GREEN SVG+welcome.ts+globals.d.ts3f8e31aTask 3 — A35 driver + A17.8 narrowed + back-patch + correctiond66cbf6Task 4 artifact — operator-empirical screenshot harness (Task 4 first operator empirical: TWEAK verdict 2026-05-26)a8bcc17debug-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
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 tovar(--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'scolor, 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 buildfound: ":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
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.
- 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. - src/welcome/welcome.css
.welcome-hero__mark— flippedcolor: 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 aa35Probe(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). SVGsrc/shared/brand/mokosh-mark.svgUNCHANGED.
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)