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>
30 KiB
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 overridessrc/shared/fonts/*.woff2(8 files, ~155 KB total) — self-hosted under MV3 CSPsrc/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)+currentColorSVG — 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__markhas 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:
- Lowest coupling. A 1-character edit to the SVG (
#181b2a→currentColor) + the existing.dark/@mediainfrastructure in tokens.css handles every present and future render surface. No per-surface override CSS. - Canonical SVG-theme pattern.
currentColoris the W3C-blessed mechanism for theming SVG strokes via parent CSScolor; 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) - 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.darkclass fallback both work; users on macOS/Windows/Linux with system dark mode get the contrast for free without manual config. - 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 inheritscolor: var(--mks-fg-inverse)→ renders as linen-50 (off-white) ON the madder-orange BG → contrast PRESERVED. The existing welcome-hero appearance is unchanged. - 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.
- Zero new tokens. Zero new copy. Zero new components. Surgical, additive, reversible (
git reverton 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
- 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__markresolves to--mks-linen-50= #faf7f1, and the inline SVG inherits this viacurrentColor). - 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-inverseoverridden to--mks-ink-900(deep indigo) per tokens.css lines 234-251.darkblock → 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). - 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 canonicalviewBox='0 0 32 32'. After Phase 4 lands, A17.8 asserts the welcome chunk contains the raw SVG source as a string literal withcurrentColor+ canonicalviewBox='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 andcurrentColorstroke resolves via parent CSS color.) - PNG toolbar icons unchanged.
icons/icon{16,48,128}.pngbyte-identical to current commit hashes;tests/build/icons-present.test.ts15-case suite remains GREEN without modification. - MV3 CSP self-host invariant unchanged. Inline SVG injection uses DOMParser + appendChild — no
innerHTML, noeval, no new script-src cost. Pre-checkpoint bundle gate perfeedback-pre-checkpoint-bundle-gates.mdremains 6/6 GREEN. - 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 ofcurrentColorresolution + 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
.darkblock in tokens.css is canonical and future-ready, but Phase 4 does NOT introduce a.darktoggle on the welcome page. Phase 4 ships the SVGcurrentColor+ 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 onbody.welcomeor 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 —
#181b2aliteral →currentColorCSS-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)