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>
71 lines
3.7 KiB
TypeScript
71 lines
3.7 KiB
TypeScript
// tests/welcome/inline-svg.test.ts — Plan 04-06 Wave 0 RED → GREEN unit test
|
|
// for the UI-SPEC dark-logo `currentColor` strategy (Option A).
|
|
//
|
|
// SCOPE — source-contract pin only (node-env file-read + string assertions).
|
|
//
|
|
// vitest is configured with `environment: 'node'` (vitest.config.ts:18) and
|
|
// the project ships no DOM-emulation library — neither jsdom, happy-dom nor
|
|
// linkedom is in node_modules. This test pins the SOURCE TEXT contract of
|
|
// the inline-SVG injection strategy (the SVG source carries the
|
|
// `currentColor` stroke, welcome.ts uses the `?raw` import + DOMParser +
|
|
// replaceChildren, globals.d.ts declares the `*.svg?raw` ambient module).
|
|
//
|
|
// It does NOT instantiate any document, does NOT call populateMark, does
|
|
// NOT invoke DOMParser at runtime. The runtime behavior (live <svg>
|
|
// injection into the welcome page DOM and the currentColor CSS cascade
|
|
// through `.welcome-hero__mark { color: var(--mks-mark-stroke); }` — the
|
|
// theme-INDEPENDENT brand-component token introduced by Plan 04-06 Task 4
|
|
// operator-empirical debug-decouple) is verified by the host-side UAT
|
|
// harness assertion A35 (driveA35 in tests/uat/lib/harness-page-driver.ts)
|
|
// — A35 opens welcome.html as a real Puppeteer tab so populateMark()
|
|
// actually runs, then reads `getComputedStyle().stroke` on the injected
|
|
// <svg> in BOTH light and dark themes to prove (a) the cascade resolves
|
|
// and (b) the resolved stroke is identical across themes (linen-50). This
|
|
// split mirrors the project's two-layer test approach (source contract
|
|
// under vitest + live-DOM behavior under the Puppeteer harness) — verified
|
|
// at re-plan iter-2 (BLOCKER 1 resolution) + Plan 04-06 Task 4 decouple.
|
|
//
|
|
// References:
|
|
// - .planning/phases/04-harden-clean-up-optional/04-UI-SPEC.md
|
|
// §"Implementation amendment" (currentColor + inline <svg> technique)
|
|
// - .planning/phases/04-harden-clean-up-optional/04-RESEARCH.md
|
|
// §SVG currentColor cascade (W3C SVG2 §13.3)
|
|
// - tests/i18n/manifest-i18n.test.ts (the canonical node-env file-read
|
|
// + string-assertion scaffold this test mirrors).
|
|
// - https://vite.dev/guide/assets.html#importing-asset-as-string
|
|
// (Vite `?raw` query suffix returns module content as a string).
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve as resolvePath } from 'node:path';
|
|
|
|
const MARK_SVG_PATH = resolvePath(
|
|
process.cwd(),
|
|
'src/shared/brand/mokosh-mark.svg',
|
|
);
|
|
const WELCOME_TS_PATH = resolvePath(process.cwd(), 'src/welcome/welcome.ts');
|
|
const GLOBALS_DTS_PATH = resolvePath(process.cwd(), 'globals.d.ts');
|
|
|
|
describe('UI-SPEC dark-logo currentColor strategy — source-level contract', () => {
|
|
it('mokosh-mark.svg: root <svg> uses stroke="currentColor" + canonical viewBox; legacy #181b2a stroke removed', () => {
|
|
const text = readFileSync(MARK_SVG_PATH, 'utf8');
|
|
expect(text).toContain('stroke="currentColor"');
|
|
expect(text).toContain('viewBox="0 0 32 32"');
|
|
expect(text).not.toContain('stroke="#181b2a"');
|
|
});
|
|
|
|
it('welcome.ts: uses `?raw` import + DOMParser + replaceChildren; no `?url` import, no innerHTML (MV3 CSP discipline / T-04-06-01)', () => {
|
|
const text = readFileSync(WELCOME_TS_PATH, 'utf8');
|
|
expect(text).toContain('mokosh-mark.svg?raw');
|
|
expect(text).toContain('DOMParser');
|
|
expect(text).toContain('replaceChildren');
|
|
expect(text).not.toContain('mokosh-mark.svg?url');
|
|
expect(text).not.toContain('innerHTML');
|
|
});
|
|
|
|
it('globals.d.ts: declares the `*.svg?raw` ambient module (mirror of the existing `*.svg?url` block)', () => {
|
|
const text = readFileSync(GLOBALS_DTS_PATH, 'utf8');
|
|
expect(text).toContain("declare module '*.svg?raw'");
|
|
});
|
|
});
|