Files
Mark 266aa95235 docs(04): UI-SPEC.md status approved — 5/6 PASS + 1 FLAG non-blocking (dim 4 inherited type scale)
UI-checker verdict: APPROVED. Dimension breakdown:
- 1 Copywriting: PASS (17-key matrix inherited + locked; zero new copy)
- 2 Visuals: PASS (no new screen; dark-logo is stroke binding change)
- 3 Color: PASS (Loom palette inherited; semantic accents declared)
- 4 Typography: FLAG (8 sizes / 4 weights exceed standard thresholds but
  correctly captured as Phase 1-locked inherited from operator brand-fit
  ack 2026-05-20; Phase 4 adds zero new sizes/weights) — non-blocking
- 5 Spacing: PASS (all multiples of 4; locked; no new values)
- 6 Registry Safety: PASS (vanilla DOM + DOMParser; no shadcn; no third-party)

Three checker observations addressed:
1. `?url` → `?raw` bundling: correctly preserves @crxjs auto-WAR (SVG
   content stays in JS bundle as string literal vs base64 data URL)
2. A17.8 sub-check update: concrete enough (raw-SVG-source string-search
   for `currentColor` + `viewBox='0 0 32 32'`); optional A17.8a/A17.8b split
   well-described
3. Dark-mode contrast: deep-indigo stroke on madder-orange wrapper is
   readable; operator empirical checkpoint (acceptance criterion #6) is
   the designated gate for WCAG ratio judgment

Implementation contract = 5 file edits + 6 acceptance criteria. Planner can
now use UI-SPEC as design context for the visual-polish Phase 4 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:46:34 +02:00

30 KiB
Raw Permalink Blame History

phase: 4 slug: 04-harden-clean-up-optional status: approved reviewed_at: 2026-05-20 reviewer: gsd-ui-checker (5 PASS + 1 FLAG non-blocking on inherited type scale) shadcn_initialized: false preset: none created: 2026-05-20 scope_kind: thin-design-surface ui_surface: dark-logo-contrast-only inherits_from: - 01-12-SUMMARY.md (design system locked: Lora display + IBM Plex Sans UI + Loom palette + Mokosh mark + canonical tokens.css + 16 i18n keys with en↔ru parity) - 01-10-SUMMARY.md (welcome page pattern: data-mokosh-slot + data-mokosh-key + data-mokosh-i18n-key three-pipeline DOM population; 17-key i18n matrix post-notifStartup split) - 03-UI-SPEC.md (null-spec precedent — Phase 4 deviates because dark-logo IS real designer work) in_scope: - dark-surface logo contrast strategy (Plan 01-10 obs 2026-05-20 — operator: "also on dark surfaces probably either we need to place the logo on the light background or dunno") behavioral_inheritance_only: - getDisplayMedia cursor visibility constraint video: { cursor: 'always' } (Plan 01-07 obs 2026-05-15) — 1-line change in src/offscreen/recorder.ts per Chrome CursorCaptureConstraint enum; operator-perceptible but NOT a design surface

Phase 4 — UI Design Contract

Visual and interaction contract for frontend phases. Generated by gsd-ui-researcher, verified by gsd-ui-checker.


Scope Determination

Phase 4 has ONE thin design surface in scope: dark-surface logo contrast.

Unlike Phase 3's null-spec, Phase 4 carries a genuine designer-side decision. Per .plan-phase-preferences.md (2026-05-20):

"If /gsd-ui-phase 4 surfaces the 'is this a UI phase?' question, answer 'yes — but narrow scope (dark-logo design only; cursor is behavioral)'"

Designer-side decision (THIS DOCUMENT decides)

Mokosh-mark dark-surface contrast strategy — the canonical mark at src/shared/brand/mokosh-mark.svg hardcodes stroke="#181b2a" (--mks-ink-900, deep indigo). On the welcome page hero (Plan 01-10) the mark already renders inside a .welcome-hero__mark wrapper with background: var(--mks-rec) (madder-orange #b2543d) → strong contrast per D-04 Loom palette intent. But on ANY browser/OS dark-mode-aware rendering surface where the SVG renders directly on the system background (e.g. operator's browser running in dark theme rendering a saved screenshot or future Phase 4-introduced dark-mode-aware surface), the dark-ink stroke loses contrast.

Cycle-1 operator empirical 2026-05-20: "also on dark surfaces probably either we need to place the logo on the light background or dunno." — observation is real but resolution latitude is designer-side.

Behavioral-only inheritance (NOT a design surface)

getDisplayMedia cursor visibility constraint (Plan 01-07 obs 2026-05-15) — adding video: { cursor: 'always' } to the existing navigator.mediaDevices.getDisplayMedia(...) call site in src/offscreen/recorder.ts per Chrome's CursorCaptureConstraint enum (always / motion / never). The captured frames will then show the operator's cursor — operator-perceptible (highest-signal cue for pointer-driven bug reproduction) but NO design contract decision required. This is listed here for awareness; the planner will route it directly through normal Phase 4 plan tasks.


Design System (INHERITED — DO NOT MODIFY)

The design system is closed for Phase 4 EXCEPT for the surgical dark-logo strategy described in this document. Plan 01-12 Wave 7 operator brand-fit ack 2026-05-20 verbatim "all good" sealed the canonical artifacts. Plan 01-10 Wave 4 cycle-2 operator ack 2026-05-20 verbatim "All good" sealed the welcome surface. Phase 4 must NOT introduce new tokens, new fonts, new colors, new copy keys, new icons (beyond the SVG stroke recolor described below), new layout primitives, OR new component patterns into the user-facing surface.

Property Value Source
Tool none (manual canonical token system) Plan 01-12 Wave 1 Task 2 — src/shared/tokens.css
Preset not applicable
Component library none (vanilla DOM; three-pipeline populate pattern for welcome page) Plan 01-10 patterns-established
Icon library static PNG artifacts at icons/icon{16,48,128}.png (8-bit RGBA; Loom mark rasterized from src/shared/brand/mokosh-mark.svg via scripts/rasterize-icons.sh) Plan 01-12 Wave 2
Display font "Lora", "Iowan Old Style", "Times New Roman", serif (Cyreal foundry; OFL-1.1; full Cyrillic via R2 substitution 2026-05-19) Plan 01-12 Wave 1 Task 2 + brand-decisions-v1-followup-display-font.md
UI font "IBM Plex Sans", "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif (OFL-1.1; full Cyrillic) src/shared/tokens.css line 146
Mono font "IBM Plex Mono", "SF Mono", Menlo, Consolas, monospace (OFL-1.1; full Cyrillic) src/shared/tokens.css line 147

Read-only canonical sources:

  • src/shared/tokens.css (~355 lines) — single source of truth; ALREADY contains a .dark, [data-theme="dark"] block at lines 234-251 with full dark-theme token overrides
  • src/shared/fonts/*.woff2 (8 files, ~155 KB total) — self-hosted under MV3 CSP
  • src/shared/brand/{mokosh-mark.svg,mokosh-lockup.svg} — canonical brand assets
  • _locales/{en,ru}/messages.json — 17 i18n keys per locale (Plan 01-12 + Plan 01-10 closure-cycle split notifStartup → notifStartupCta + notifRecordingStarted)
  • icons/icon{16,48,128}.png — committed static artifacts (NOT regenerated at build time)

Spacing Scale (INHERITED)

Declared values from src/shared/tokens.css lines 178-188. Phase 4 must not introduce new spacing values.

Token Value Usage
--mks-space-1 4px Icon gaps, inline padding
--mks-space-2 8px Compact element spacing
--mks-space-3 12px (used by component CSS as needed)
--mks-space-4 16px Default element spacing
--mks-space-5 20px (used by component CSS as needed)
--mks-space-6 24px Section padding
--mks-space-8 32px Layout gaps
--mks-space-10 40px (used by component CSS as needed)
--mks-space-12 48px Major section breaks
--mks-space-16 64px Page-level spacing
--mks-space-20 80px (used by component CSS as needed)

Exceptions: none in Phase 4 (the dark-logo strategy below does not add new spacing).


Typography (INHERITED)

Declared values from src/shared/tokens.css lines 151-167 + semantic helpers lines 254-333. Phase 4 must not introduce new font sizes or weights.

Role Size Weight Line Height Token / class
Body 15px 400 (regular) 1.5 .mks-body
Label (small) 13px 400 1.5 .mks-body-sm
Heading 17px 600 (semibold) 1.3 .mks-h3
Heading large 20px 600 1.3 .mks-h2
Heading display 28px 400 1.15 .mks-h1
Display 40px (or 56px for hero) 400 1.15 .mks-display-2 / .mks-display-1

Inherited type scale: 11, 13, 15, 17, 20, 28, 40, 56 px. Phase 4 must not add intermediate sizes.

Inherited weights: 400 regular, 500 medium, 600 semibold, 700 bold.


Color (INHERITED — Loom palette per D-04)

Declared values from src/shared/tokens.css lines 89-137. Phase 4 must not introduce new colors.

Role Value Usage
Dominant (60%) --mks-linen-50 = #faf7f1 Page background
Surface raised (cards/popup) --mks-linen-100 = #f3eee4 Card / popup surface
Secondary (30%) --mks-linen-200 = #e8e0d0 Hairline / divider on linen
Foreground primary --mks-ink-900 = #181b2a Primary text, deepest surface, current canonical mark stroke
Foreground inverse --mks-linen-50 = #faf7f1 Foreground on dark surfaces (fg-inverse token)
Accent (10%) — REC --mks-madder-600 = #b2543d Recording state badge + REC dot + welcome hero mark BG only
Accent — success --mks-moss-600 = #5a7349 "Готово ✓" SAVE-success state only
Accent — warning --mks-amber-600 = #c98b3a Recoverable error / SAVING transient state only
Destructive --mks-brick-600 = #a23a2b Unrecoverable error state only

Phase 4 must NOT introduce additional accent surfaces. The dark-mode surfaces consume the existing .dark, [data-theme="dark"] block at src/shared/tokens.css lines 234-251 (already canonical).

Accent reserved for: REC badge background; REC pulse-dot; SAVE button primary-CTA fill (madder-700 at hover); SAVING transient (amber-600); DONE transient (moss-600); ERROR (brick-600); welcome-hero mark wrapper background (madder-600). Per Plan 01-12 Wave 4 BADGE_REC_COLOR flip from material-green to madder + Plan 01-10 welcome-hero pattern.


Copywriting Contract (INHERITED — read-only)

All operator-facing copy is locked. Phase 4 must NOT add operator-facing strings (the dark-logo strategy adds ZERO new copy — the alt-text fallback already exists at welcome.hero.mark.alt in COPY map and the canonical Russian default Знак Mokosh at src/welcome/welcome.ts line 163).

Element Copy Source
Extension name (EN) "Mokosh — Session Capture" _locales/en/messages.json extName (D-07)
Extension name (RU) "Mokosh — Запись сессии" _locales/ru/messages.json extName
Extension description (EN) "Thirty seconds ago, always at hand." _locales/en/messages.json extDesc (D-08)
Extension description (RU) "Тридцать секунд назад, всегда под рукой." _locales/ru/messages.json extDesc
Primary CTA (popup SAVE) "Сохранить отчёт об ошибке" (popupSaveCta) Plan 01-09 + REQ-popup-ui
Empty state heading "Готов к записи" (popupSavePrompt + popupInfoText) Plan 01-12 Wave 3
Saving transient "Сохраняю..." (popupSaving) Plan 01-09 state machine
Done transient "Готово! ✓" (popupSaveDone) Plan 01-09 state machine
Startup notification CTA "Mokosh ready. Click to start a recording." (notifStartupCta — split from notifStartup per Plan 01-10 closure-cycle debug 4bba679) Plan 01-10 cycle-1 debug fix
Recording started notification (notifRecordingStarted) Plan 01-10 cycle-1 debug fix
Recovery notification (notifRecovery) Plan 01-09 + Plan 01-12
Welcome hero (RU) "Тридцать секунд назад, всегда под рукой." (welcomeHeroRu) Plan 01-10 D-08
Welcome hero (EN) "Thirty seconds ago, always at hand." (welcomeHeroEn) Plan 01-10 D-08
Toolbar tooltip (off / rec / err) tooltipOff / tooltipRecPrefix / tooltipErr Plan 01-12 Wave 3
Mokosh mark alt (RU graceful-degradation) "Знак Mokosh" (COPY['welcome.hero.mark.alt'] fallback in welcome.ts:163) Plan 01-10 Wave 4 cycle-1 mark-bundling debug

Destructive actions in Phase 4: none. Phase 4 has no buttons, no confirmations, no UI state machine changes. The dark-logo strategy is a static stroke recolor + a CSS rule.


Dark-Surface Logo Contrast Strategy (PHASE 4 IN-SCOPE DECISION)

Decision: Option A — currentColor SVG + CSS color driven via prefers-color-scheme

One-line summary: Convert the hardcoded stroke="#181b2a" in src/shared/brand/mokosh-mark.svg to stroke="currentColor"; let the rendering container's CSS color property drive the stroke; the existing .dark, [data-theme="dark"] block at src/shared/tokens.css lines 234-251 already provides the dark-surface inverse-token swap.

Rationale (why A over B, C, D)

This is a designer-side recommendation. The four options surfaced in CONTEXT.md <specifics>:

  • (A) media query @media (prefers-color-scheme: dark) + currentColor SVG — canonical SVG-theme pattern; works on any rendering surface; respects user's OS-level dark-mode preference automatically; no new tokens; minimum coupling.
  • (B) wrap logo in tinted neutral background (white-circle/off-white card) — the welcome page ALREADY does this (.welcome-hero__mark has madder-600 BG → dark ink stroke contrasts strongly); this is the existing pattern for the only current first-class render surface. Wrapping every future surface in a neutral background is brittle and clashes with the warm-earthy Loom palette intent (D-04).
  • (C) swap to a contrast-tested neutral palette across all surfaces — would re-open D-04 brand-palette decision; out of scope for Phase 4 hardening and would break the operator brand-fit ack 2026-05-20 "all good".
  • (D) other — not needed; option A is the standard.

Why A wins:

  1. Lowest coupling. A 1-character edit to the SVG (#181b2acurrentColor) + the existing .dark/@media infrastructure in tokens.css handles every present and future render surface. No per-surface override CSS.
  2. Canonical SVG-theme pattern. currentColor is the W3C-blessed mechanism for theming SVG strokes via parent CSS color; it's the standard approach used by lucide-icons, heroicons, phosphor-icons, and every major icon library. (Reference: https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint)
  3. Respects OS dark-mode preference. @media (prefers-color-scheme: dark) (already a sibling of the existing @media (prefers-reduced-motion: reduce) block at tokens.css lines 336-342) plus the explicit .dark class fallback both work; users on macOS/Windows/Linux with system dark mode get the contrast for free without manual config.
  4. Preserves the welcome-hero pattern. The mark on the welcome page is wrapped in .welcome-hero__mark { background: var(--mks-rec); color: var(--mks-fg-inverse); } (welcome.css lines 64-73). After the SVG recolor, the stroke inherits color: var(--mks-fg-inverse) → renders as linen-50 (off-white) ON the madder-orange BG → contrast PRESERVED. The existing welcome-hero appearance is unchanged.
  5. Survives future dark-mode-aware surfaces. Any future Phase 4+ surface (dark popup variant, smoke diagnostic overlay, dark-theme welcome variant — the tokens.css comment at line 232-233 even anticipates these) gets correct contrast without touching the SVG again.
  6. Zero new tokens. Zero new copy. Zero new components. Surgical, additive, reversible (git revert on the SVG file restores prior behavior).

Implementation contract (planner consumes this)

File: src/shared/brand/mokosh-mark.svg

<!-- BEFORE (current canonical) -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"
     stroke="#181b2a" stroke-width="2.25" stroke-linecap="square"
     stroke-linejoin="miter">

<!-- AFTER -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"
     stroke="currentColor" stroke-width="2.25" stroke-linecap="square"
     stroke-linejoin="miter">

Only stroke="#181b2a"stroke="currentColor" on the root <svg> element. The 12 child <line> and 1 child <rect> elements inherit stroke from the SVG root; they remain unchanged.

File: src/welcome/welcome.css (adjust .welcome-hero__mark-img rule lines 91-95)

Add color: inherit; to .welcome-hero__mark-img so the SVG currentColor resolves from the wrapper's color: var(--mks-fg-inverse) (already at .welcome-hero__mark line 72). Or rely on default CSS color inheritance (.welcome-hero__mark-img is a direct child of .welcome-hero__mark which sets color: var(--mks-fg-inverse), so inheritance happens automatically — verify in implementation).

Wait — the SVG is bundled as <img> via Vite ?url import (welcome.ts line 46 + 166). <img> elements do NOT inherit color for SVG currentColor resolution because they render the SVG as an opaque image, not as inline DOM. This means option A as drafted above fails for the welcome page hero.

Implementation amendment (post-research)

The welcome page consumes the mark via <img src={markUrl}> (Vite ?url import yields a data:image/svg+xml,... data URL for assets ≤4096 B). <img>-rendered SVGs run in an isolated SVG document context — they do NOT inherit the parent CSS color. The stroke="currentColor" resolves to the SVG's own root color, which defaults to canvastext (black on most user agents).

Two-part solution:

Part 1: Inline the SVG into the DOM (replace <img src={markUrl}> with inline <svg> injected at populateMark() time). The Vite ?url import becomes a Vite ?raw import that returns the SVG source text; populateMark() parses + inserts the SVG as a DOM child of .welcome-hero__mark. Inline-DOM SVGs DO inherit parent CSS color, so stroke="currentColor" resolves correctly.

// AT welcome.ts line 46:
// BEFORE: import markUrl from '../shared/brand/mokosh-mark.svg?url';
// AFTER:  import markSvg from '../shared/brand/mokosh-mark.svg?raw';

// AT welcome.ts populateMark() function:
// BEFORE: const img = document.createElement('img'); img.src = markUrl; ...
// AFTER: parse markSvg via DOMParser; append the <svg> root to the slot;
//        apply class='welcome-hero__mark-img' to the inline <svg>; preserve
//        aria-hidden + role='img' + aria-label attributes.

Part 2: PNG icons keep the hardcoded dark stroke. Toolbar icons at icons/icon{16,48,128}.png are static rasterizations from the SVG (8-bit RGBA via scripts/rasterize-icons.sh). They render in chrome://extensions settings, the toolbar, and the Chrome menu — all surfaces that Chrome owns and that DO respect Chrome's own dark-theme handling (Chrome auto-inverts dark icons on dark toolbars). PNG icons do NOT consume CSS color (they're rasterized pixels). They keep stroke="#181b2a" baked into their pixels per the existing scripts/rasterize-icons.sh recipe. No change needed for PNG icons.

Net file-change set (planner reference):

File Change Lines affected
src/shared/brand/mokosh-mark.svg stroke="#181b2a"stroke="currentColor" on root <svg> Line 2
src/welcome/welcome.ts ?url import → ?raw import + populateMark() rewrite (DOMParser-based inline-SVG injection) Lines 46, 159-179
src/welcome/welcome.css (optional) .welcome-hero__mark-img styled to target both img. and svg. selectors during transition Lines 91-95
globals.d.ts Add ambient module declaration for *.svg?raw (alongside existing *.svg?url) append 1 block
tests/uat/extension-page-harness.ts (A17.8) Update sub-check to detect inline SVG presence in the welcome chunk (not the data URL) Wave 3 b112cb7
scripts/rasterize-icons.sh NO CHANGE (PNG icons keep ink-stroke; Chrome handles toolbar dark-mode)
icons/icon{16,48,128}.png NO CHANGE (rasterized pixels; Chrome auto-inverts on dark toolbars)
src/shared/tokens.css NO CHANGE (existing .dark, [data-theme="dark"] block at lines 234-251 already handles inverse tokens)

Acceptance criteria for the dark-logo strategy

  1. Welcome-hero appearance unchanged on light surface. Operator loads extension; opens welcome page; mark renders inside madder-600 circle with linen-50 (off-white) stroke — identical to current Plan 01-10 cycle-2 ack appearance (since color: var(--mks-fg-inverse) at .welcome-hero__mark resolves to --mks-linen-50 = #faf7f1, and the inline SVG inherits this via currentColor).
  2. Welcome-hero appearance unchanged on dark surface. Operator with OS dark-mode enabled, OR future dark-theme welcome variant via .dark/[data-theme="dark"], sees the mark rendered with --mks-fg-inverse overridden to --mks-ink-900 (deep indigo) per tokens.css lines 234-251 .dark block → on dark surface, ink stroke contrasts strongly against the dark background; the wrapper's madder BG remains (REC accent is dark-mode-stable per D-04 palette).
  3. A17.8 sub-check updated. The Plan 01-10 harness sub-check A17.8 currently asserts the welcome chunk JS contains the inlined data:image/svg+xml,... data URL with canonical viewBox='0 0 32 32'. After Phase 4 lands, A17.8 asserts the welcome chunk contains the raw SVG source as a string literal with currentColor + canonical viewBox='0 0 32 32'. (Or A17.8 becomes A17.8a + A17.8b: A17.8a — raw SVG source bundled; A17.8b — inline <svg> injected at populateMark() time and currentColor stroke resolves via parent CSS color.)
  4. PNG toolbar icons unchanged. icons/icon{16,48,128}.png byte-identical to current commit hashes; tests/build/icons-present.test.ts 15-case suite remains GREEN without modification.
  5. MV3 CSP self-host invariant unchanged. Inline SVG injection uses DOMParser + appendChild — no innerHTML, no eval, no new script-src cost. Pre-checkpoint bundle gate per feedback-pre-checkpoint-bundle-gates.md remains 6/6 GREEN.
  6. Plan 01-10 cycle-2 ack-equivalent UAT. Operator empirical re-validation on a dark-mode-enabled OS (macOS dark mode OR Chrome dark theme OR Windows dark mode — pick whichever the operator runs) confirms the mark contrasts on a dark surface. This is the ONE Phase 4 operator-empirical checkpoint required by the dark-logo strategy. Per feedback-trust-harness-over-manual-uat.md, harness coverage of currentColor resolution + inline-SVG injection is sufficient for the automated path; operator confirms the dark-surface aesthetic.

Out of scope for the dark-logo strategy

  • A dedicated dark-theme welcome variant. The .dark block in tokens.css is canonical and future-ready, but Phase 4 does NOT introduce a .dark toggle on the welcome page. Phase 4 ships the SVG currentColor + inline-injection path so that IF a future plan adds a dark welcome variant, the mark already contrasts correctly. No new dark-mode CSS classes on body.welcome or the welcome-hero.
  • A separate dark-variant SVG file. Option A's strength is one SVG file driven by CSS color; we explicitly reject the two-SVG approach (mokosh-mark.svg + mokosh-mark-dark.svg).
  • An icon-library migration. Mokosh stays on its single hand-authored canonical brand asset.

Test Fixture Conventions (INHERITED from Phase 3)

Phase 4 may extend tests/uat/extension-page-harness.ts with A33+ assertions for the dark-logo strategy + the other Phase 4 hardening items. The probe-page conventions established by Phase 3's UI-SPEC remain canonical:

Convention Reference
Probe HTML does NOT import canonical src/shared/tokens.css (the harness page does it once at the top) 03-UI-SPEC.md Test Fixture Conventions
Probe HTML does NOT use var(--mks-*) tokens 03-UI-SPEC.md
Probe HTML does NOT import _locales/* strings via chrome.i18n 03-UI-SPEC.md
Probe HTML does NOT inject data-mokosh-slot / data-mokosh-key / data-mokosh-i18n-key attributes 03-UI-SPEC.md
Probe HTML MAY use plain data-test-* attributes for querySelector targeting 03-UI-SPEC.md

FORBIDDEN_HOOK_STRINGS lockstep: Phase 4 inherits the 12-entry inventory baseline from Plan 01-10 (12 entries) + Plan 01-12 (+1 = 13) + Plan 03-* (unchanged at 12 or 13 depending on plan-checker audit). If A33+ adds new test-mode symbols, those must be __MOKOSH_UAT__-gated AND added to tests/background/no-test-hooks-in-prod-bundle.test.ts. Per CONTEXT.md <decisions> Claude's Discretion: "Tier-1 FORBIDDEN_HOOK_STRINGS stays at 12 unless A33+ needs new MOKOSH_UAT-gated symbols (audit at plan time; flag if increment needed)."


Registry Safety

Registry Blocks Used Safety Gate
shadcn official none — manual canonical token system not applicable
Third-party none not applicable

Phase 4 introduces no third-party UI dependencies. The dark-logo strategy edits the hand-authored canonical Mokosh mark SVG.


Inherited Visual Contracts to Preserve

Phase 4 verification will incidentally exercise these contracts (via existing harness assertions A0-A32 that MUST remain GREEN per CONTEXT.md <code_context> Integration Points). Phase 4 must not regress any of them.

Contract Source How Phase 4 preserves it
MV3 CSP self-host invariant (0 googleapis / 0 https://fonts in dist/) Plan 01-12 Wave 1 Task 2 + tests/build/no-remote-fonts.test.ts Phase 4 ships no new fonts; SVG inline-injection uses DOMParser (no innerHTML); pre-checkpoint bundle gate re-verifies
en↔ru i18n parity (17 keys × 2 locales) Plan 01-12 Wave 3 + Plan 01-10 cycle-1 split + tests/i18n/locale-parity.test.ts Phase 4 ships no new locale keys; pre-checkpoint bundle gate re-verifies
Icons rendering at 16/48/128 (8-bit RGBA) Plan 01-12 Wave 2 + tests/build/icons-present.test.ts + harness A19 Phase 4 does not modify PNG icons; A19 remains GREEN
--mks-font-display resolves to Lora stack Plan 01-12 Wave 1 Task 2 + harness A21 Phase 4 does not modify tokens.css; A21 remains GREEN
--mks-rec resolves to rgb(178, 84, 61) = #b2543d Plan 01-12 Wave 4 BADGE_REC_COLOR + harness A17.7 Phase 4 does not modify palette; A17.7 remains GREEN
BADGE_REC_COLOR madder + 3-state state machine Plan 01-09 + Plan 01-12 Wave 4 Phase 4 makes no badge changes
Welcome page first-install activation (onInstalled('install') + storage flag + tabs.create) Plan 01-10 Wave 2 helper + harness A15/A16/A17 Phase 4 makes no onboarding changes
Welcome-hero mark visible (canonical Mokosh SVG bundled) Plan 01-10 Wave 4 cycle-1 mark-bundling debug + A17.8 A17.8 sub-check UPDATED — see "Dark-Surface Logo" Acceptance Criterion #3 above
Welcome-hero appearance on light surface unchanged Plan 01-10 Wave 4 cycle-2 ack 2026-05-20 "All good" After currentColor + inline-SVG land, color: var(--mks-fg-inverse) cascades from .welcome-hero__mark wrapper → linen-50 stroke on madder-orange BG → visually identical to cycle-2 ack

Behavioral-Only Inheritance (NOT a Design Surface)

These items are listed for awareness; they do NOT solicit designer input. Phase 4 planner routes them through normal plan tasks.

Item File Change Operator-perceptible? Design contract?
getDisplayMedia cursor visibility constraint src/offscreen/recorder.ts Add video: { cursor: 'always' } to navigator.mediaDevices.getDisplayMedia(...) constraints per Chrome CursorCaptureConstraint enum YES — captured frames will include the operator's cursor (highest-signal cue for pointer-driven bug reproduction; Plan 01-07 obs 2026-05-15) NO — no visual surface; no copy; no UI state

Reference: https://www.w3.org/TR/screen-capture/#dom-cursorcaptureconstraint


Checker Sign-Off

This UI-SPEC declares ONE thin design surface (dark-logo contrast) with a designer-recommended strategy + amendment for the inline-SVG technical requirement. The 6 design quality dimensions are evaluated against the inherited surface + the dark-logo strategy.

  • Dimension 1 Copywriting: PASS (inherited; 17-key matrix locked; dark-logo strategy adds ZERO new operator copy)
  • Dimension 2 Visuals: PASS (canonical brand artifacts unchanged in shape; only stroke color binding changes — #181b2a literal → currentColor CSS-driven; visual appearance on the existing welcome-hero surface is identical to Plan 01-10 cycle-2 ack)
  • Dimension 3 Color: PASS (inherited; Loom palette locked at src/shared/tokens.css; no new colors; dark-logo strategy consumes existing .dark, [data-theme="dark"] block at lines 234-251)
  • Dimension 4 Typography: PASS (inherited; 3 families × declared sizes/weights locked; no new sizes)
  • Dimension 5 Spacing: PASS (inherited; --mks-space-* scale locked; 4px base preserved)
  • Dimension 6 Registry Safety: PASS (no shadcn use; no third-party registry; vanilla DOM + DOMParser inline SVG)

Approval: pending checker review (draft status)


Notes for Downstream Phase 4 Agents

Planner (gsd-planner): Phase 4 has ONE genuine design surface (dark-logo contrast — see "Dark-Surface Logo Contrast Strategy" section). Allocate it to a small dedicated plan (suggested as plan 04-06 per CONTEXT.md <decisions> Claude's Discretion: "04-06: Visual polish — cursor visibility constraint + dark-logo contrast (operator-perceptible)"). The implementation contract above lists the 5 file edits + 4 acceptance criteria. The cursor-visibility constraint is behavioral-only and can co-land in the same plan.

Executor (gsd-executor): When editing src/shared/brand/mokosh-mark.svg, change ONLY stroke="#181b2a" to stroke="currentColor" on the root <svg> element. Do NOT modify the 13 child elements (1 <rect> + 12 <line>s). When editing src/welcome/welcome.ts, swap the ?url import for ?raw, then rewrite populateMark() to use DOMParser to inject the inline <svg> (preserving aria-hidden + role + canonical class names). NEVER use innerHTML — DOMParser + appendChild only (MV3 CSP discipline).

UI auditor (if spawned): Audit the Phase 1 + Phase 2 + Phase 3 surfaces remain unchanged post-Phase-4. The dark-logo strategy's visual diff is intentional and approved by this document.

Ceremony note: If during execution the planner OR executor discovers the dark-logo strategy needs operator input on the recolor approach (e.g. the operator expresses a preference for option B/C/D over A), route through /gsd-debug or /gsd-discuss-phase per feedback-gsd-ceremony-for-fixes.md. Do not hot-edit this UI-SPEC or the canonical design system.


Phase: 04-harden-clean-up-optional UI-SPEC drafted: 2026-05-20 Designer-side decision logged: Option A — currentColor SVG + CSS color driven via existing .dark, [data-theme="dark"] block in tokens.css; inline SVG injection in welcome.ts via ?raw import + DOMParser (amendment after <img> currentColor isolation discovered)