Operator-empirical Task 4 checkpoint flagged the dark-mode mark stroke
as muddy ink-on-madder. Root cause: .welcome-hero__mark used
`color: var(--mks-fg-inverse)`, which is a SEMANTIC text-foreground-on-
inverse-surface token that flips to ink-900 in the dark theme
(tokens.css line 244). The mark sits on a theme-independent madder-600
circle, so the stroke must be theme-independent too.
Fix: introduce a dedicated BRAND-COMPONENT token --mks-mark-stroke =
var(--mks-linen-50) in the universal :root block. CRUCIALLY NOT
overridden in the .dark/[data-theme="dark"] block — stays linen-50 on
every surface. Rewire .welcome-hero__mark to point at the new token.
SVG (mokosh-mark.svg) unchanged — `stroke="currentColor"` cascade
plumbing identical; only the wrapper's color source changed.
A35 strengthened: extracted live-DOM probe into a helper, now probes
BOTH light + dark themes (data-theme="dark" toggle on documentElement),
and added A35.5 — the decouple proof that light.computedStroke ===
dark.computedStroke === "rgb(250, 247, 241)" (linen-50). No new
__MOKOSH_UAT__ symbol; FORBIDDEN_HOOK_STRINGS stays at 12.
Scope expansion note: src/welcome/welcome.css was not in Plan 04-06
re-plan iter-2 files_modified. The edit is authorized by the operator's
TWEAK verdict on Task 4 checkpoint.
Verification:
- /tmp/04-06-welcome-hero-{light,dark}.png re-shot — both show identical
crisp linen-on-madder grid icon.
- A35.5 LIVE-DOM probe (UAT): light="rgb(250, 247, 241)", dark=same.
- UAT 36/36 GREEN; vitest 187 + 1 tolerated webm-remux flake.
- 6/6 pre-checkpoint bundle gates PASS; FORBIDDEN_HOOK_STRINGS = 12.
Debug session: .planning/debug/04-06-dark-mode-mark-decouple.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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)