Files
baya-monorepo/dev/post-phase/ui/ui-phase-0-design-language.md
T
2026-07-17 13:22:04 +03:30

314 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI Phase 0 — Design language & theme foundation
> **Mission:** kill the default-MUI/starter look at the token + theme layer in one pass and establish the
> brand's visual system — a `theme.components` pass, a semantic palette, a real Persian type scale, one icon
> family, a designed Balinyaar logomark, extended tokens (elevation/motion/focus/rating/trust/money), and a
> purge of starter residue — so every later phase composes on a system that already looks designed. **No page
> redesigns here**: this phase changes what every screen inherits, not any screen's layout.
>
> **Track:** frontend · **Depends on:** — (first phase of the [UI chain](README.md)) · **Unlocks:**
> [Phase 1](ui-phase-1-primitives-and-states.md), [Phase 2](ui-phase-2-shells-and-navigation.md), and every
> later phase — they all inherit the de-startered look built here.
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md)
> and invoke the frontend-designer skill — both are mandatory.**
## 1. Context — where this sits
Balinyaar's feature layer is unusually disciplined — 331 `var(--bal-*)` usages across 103 files, effectively
zero hard-coded hexes, correct RTL/Shamsi/BigInt-money plumbing — but the *visual system underneath it is
still the karpolan react-mui starter*. Diagnosed root causes (all verified in code):
1. **`createTheme` has zero `components` overrides** — `client/src/theme/theme.ts:19-36` passes only
`cssVariables`/`colorSchemes`/`typography`/`shape`/`direction`; every Button/Card/TextField/AppBar/Chip/
Dialog/Alert renders stock Material with recolored primaries. The single biggest cause of the starter feel.
2. **No semantic palette**`theme/colors.ts` defines only primary/secondary/background/text/divider, so
inline `<Alert severity>` surfaces render stock MUI green/red while toasts (`lib/toast/NotistackProvider.tsx`)
use the brand `--bal-*` feedback tokens — two feedback systems for one semantic state. Worse, a bare
`<AppAlert>` defaults to a **filled stock-red error** (`components/config.ts:10-11`).
3. **Persian typography is Roboto metrics**`TYPOGRAPHY_RTL` (`theme/typography.ts:43-52`) sets only
`fontFamily` + weights: non-zero letter-spacing on a joined script, tight heading leading, and a requested
weight **600** (h6, button) that the Mikhak loader (`app/[locale]/layout.tsx:39-48`, weights 400/500/700)
renders as full Bold — repeated in 68 `fontWeight: 600` sx usages across 42 files (verified). Space
Grotesk (`--font-space-grotesk`) is declared for EN but never loaded.
4. **The brand mark is the starter's Twemoji cartoon pencil**`AppIcon/config.ts:115` registers
`logo: PencilIcon`; `components/auth/BrandMark.tsx:21` renders it at 56px on every auth screen and passes
`color="var(--bal-primary)"`, which the hard-coded fills (`#EA596E`, `#FFCC4D`, …) silently ignore.
5. **The icon system is broken and incoherent**`AppIcon.tsx:38-46` passes `size` as SVG `width`/`height`
*attributes*, which MUI `SvgIcon`'s `1em` class CSS overrides, so every `size={14..56}` call-site renders
24px. The ~87-entry registry mixes filled starter icons against `*Outlined` feature icons, carries eight
dead entries (zero usages, verified), and has **no back/chevron icon at all** (only `expand`).
6. **Starter residue everywhere else:** `AppButton`'s `DEFAULT_SX_VALUES = { margin: 1 }` neutralized by
**213 `m: 0` occurrences across 82 files**; dead `theme/light.ts`/`dark.ts` exported from `theme/index.ts`
incl. `LIGHT_THEME as default` (nothing else imports them); the 17-line starter `globals.css`; the wrong
`CONTENT_MIN_WIDTH` comment (`components/config.ts:5`); unused `AppImage` (all verified); a cookie-less
OS-dark first visit paints light first (`lib/cookies/server.ts:46` returns `'light'`, no pre-paint script
exists — acknowledged doc drift in `client/CLAUDE.md`); `viewport.themeColor` hard-coded to light teal
(`layout.tsx:50-52`); `public/site.webmanifest` still ships starter `#000000`/`#ffffff` and references
`img/favicon/*.png` files that don't exist.
**What already exists (do not rebuild):**
- The **two-layer token system**`theme/tokens.css` (`--bal-*`, scheme-keyed on `data-mui-color-scheme`)
mirrored by `theme/colors.ts`, sync rule in both headers. Extend it; never bypass it.
- The **MUI v9 RTL dual-theme setup** (`APP_THEME_LTR`/`APP_THEME_RTL` built once at module load,
`stylis-plugin-rtl` Emotion cache in `ThemeProvider.tsx`) and the **cookie-SSR color-scheme sync**
(`getThemeMode()``data-mui-color-scheme` stamped server-side; explicit `colorSchemeSelector`).
- **Per-locale font loading** (Mikhak via `next/font/local`, `preload: false`, attached only on `fa`),
the **AppIcon string registry** with its snake_case domain names (the exact indirection that makes this
phase's icon swap a config-level change), **brand-styled notistack toasts**, and the correct global
decisions already made: `shape.borderRadius: 10`, `textTransform: 'none'`, the lifted dark-teal palette.
## 2. Required reading (do this first)
- The audits: [audit/theme-and-brand.md](audit/theme-and-brand.md),
[audit/component-primitives.md](audit/component-primitives.md),
[audit/cross-cutting-ux.md](audit/cross-cutting-ux.md) — the full file/line evidence and the
**Keep (do not regress)** lists this phase must honor.
- [../../../.claude/skills/frontend-designer/SKILL.md](../../../.claude/skills/frontend-designer/SKILL.md) —
the design contract you are extending (invoke the skill; this phase also *updates* it — see 3.10).
- `client/CLAUDE.md` — "Golden rules", "Theme System" (the cookie/no-flash machinery and the MUI v9 traps:
explicit `colorSchemeSelector`, never `InitColorSchemeScript`, never `storageWindow`), and "Fonts".
- Code, in this order: all of `client/src/theme/`, `components/config.ts`, `components/common/AppIcon/`
(+ `icons/PencilIcon.tsx`), `components/common/AppButton/AppButton.tsx`, `components/auth/BrandMark.tsx`,
`lib/toast/NotistackProvider.tsx`, `app/[locale]/layout.tsx`, `app/globals.css`, `lib/cookies/server.ts`
(`getThemeMode`), `public/site.webmanifest`.
- `product/overview/platform-summary.md` — the tone (trust-first, calm, clinical-but-human) every visual
decision here must serve.
## 3. Scope — build this
### 3.1 The `theme.components` pass (the highest-leverage change in the app)
Add a `components` block inside `createAppTheme` (`theme/theme.ts`) that encodes the brand once. Reference
colors as `var(--bal-*)` tokens (or `theme.vars.palette.*`) so every override is scheme-correct — never a
hex. Overrides must be direction-safe (logical properties only — both theme directions share them). Cover at
least: **MuiButton** (`disableElevation`, comfortable `paddingInline`, weight per the 3.3 decision, keep
`textTransform: 'none'`); **MuiPaper/MuiCard** (hairline `1px solid var(--bal-divider)` border + soft
**teal-tinted** shadows from the 3.4 elevation tokens instead of MUI's grey stack on warm cream);
**MuiAppBar** (paper/cream surface + hairline bottom divider, **not** solid primary — shells are rebuilt in
phase 2, this stops the default bar screaming "starter" now); **MuiOutlinedInput/MuiTextField** (house
radius, calmer resting border, brand focus ring); **MuiChip / MuiToggleButton(Group) / MuiListItemButton**
(soft `--bal-primary-soft`/`--bal-secondary-soft` tonal fills; `selected` = primary-soft + primary text —
tokens already exist in both schemes); **MuiDialog** (radius 16, sane mobile margins); **MuiTabs** (thicker
indicator, comfortable min-height); **MuiAlert** (severities from the 3.2 semantic palette so inline alerts
match toasts); **MuiStepper/MuiStepIcon** (brand-colored active/completed steps — the trust flows live on
these); **MuiSkeleton** (warm tint per scheme); **MuiTooltip** (ink/cream inversion, house radius); and
**MuiCssBaseline** carrying a global `:focus-visible` treatment off `--bal-focus-ring` (2px ring + offset —
today exactly **one** `:focus-visible` style exists in the app, in `NurseResultCard.tsx`; after this,
keyboard focus is uniform everywhere).
### 3.2 Semantic palette — one feedback language
- Add `success`/`error`/`warning`/`info` (main + contrastText, light **and** dark) to
`LIGHT_PALETTE`/`DARK_PALETTE` in `colors.ts`, sourced from the existing `--bal-*` feedback values in
`tokens.css` (light: `#1f6b50`/`#a8392a`/`#8a6418`/`#1d4a40`; dark: the lifted set). Keep the two files
mirror-synced. Alert, `Chip color="success"`, Badge, LinearProgress become brand-harmonized automatically.
- Fix the `AppAlert` footgun in `components/config.ts`: first make severity explicit at any call-site that
genuinely means error, then change the defaults to a calm baseline (`severity="info"`,
`variant="standard"` on the new tinted severities — decide and document in the config file).
### 3.3 A real Persian type scale
In `theme/typography.ts` `TYPOGRAPHY_RTL`:
- **`letterSpacing: 0` on every variant** — Persian is a joined script; tracking breaks Mikhak's glyph
connections (MUI's Latin defaults set non-zero spacing on body1/body2/button/caption/overline).
- **Body line-height ≥ 1.7; headings ~1.41.5** (room for Persian ascenders/descenders) plus **responsive
heading sizes** (MUI's default 6rem h1 is unusable; grep confirms zero h1/h2 usages today).
- **Resolve the weight-600 problem.** Mikhak loads 400/500/700 only, so every requested 600 silently renders
700. Decide: (a) adopt **500/700 semantics** — map the theme's h6/button and the 68 `fontWeight: 600` sx
usages across 42 files to loaded weights (recommended: 700 headings/buttons, 500 in-text emphasis; the
sweep is mechanical) — or (b) add a genuine Mikhak DemiBold face to `src/app/fonts/` + the loader. Pick
one, do it fully, **document the decision in `typography.ts` and the skill (3.10)**.
- **Settle the EN display font**: wire Space Grotesk via `next/font` in `app/[locale]/layout.tsx` (attached
only on `en`, mirroring the Mikhak pattern, `preload: false`) **or** delete the dead
`BRAND_FONT_VARIABLE_EN` variable and its fallback stack. Decide and do it — no vaporware comment left.
### 3.4 Token extension — beyond color
Extend `theme/tokens.css` (**both scheme blocks — always**), mirroring palette-level values in `colors.ts`:
- **Elevation/shadow tokens** — 23 steps of teal-tinted shadows (light: `rgba(29,74,64,α)` stacks; dark:
black-teal), consumed by 3.1's Paper/Card/Dialog overrides.
- **Radius scale** — document the house scale (e.g. 4 / 10 / 16: controls / cards / dialogs) as tokens or
constants next to `shape.borderRadius`; stop inventing radii per component.
- **Motion tokens** — durations (e.g. 120/200/300ms) + easings; defined once here, consumed by phase 12 —
and **`--bal-focus-ring`**, the global focus ring used by 3.1's `:focus-visible` treatment.
- **`--bal-rating` + `--bal-rating-empty`** — proper star gold + empty color per scheme (`RatingInput`'s
muddy `--bal-warning` stars are fixed in **phase 1** using these); **`--bal-money-emphasis`** — a
contrast-safe money-text emphasis per scheme, retiring terracotta as a small-text money color (`#d98c6a`
on white fails AA contrast); **`--bal-trust` + `--bal-trust-soft`** — a distinct trust identity for
verified marks (not generic primary/success), consumed by phases 1/4/8. All define-only here.
- Update the stale `tokens.css` header (it cites `product/balinyaar.html`, which no longer exists in the
repo) to point at the frontend-designer skill as the brand source of truth (3.10).
### 3.5 A real brand mark — kill the pencil
- Design the Balinyaar logomark per the identity the skill records (the `product/balinyaar.html` seed deck
is **gone from the repo** — recreate from the tokens + the skill's description): deep-teal ground, cream
lowercase glyph, a single terracotta dot. Build two SVGs under `AppIcon/icons/`: a **monochrome
`currentColor` logomark** (so `color="var(--bal-primary)"` finally works and it recolors in dark mode for
free) and a **full lockup** (mark + wordmark) for `BrandMark`/auth.
- Register the mark as `ICONS.logo` (replacing `PencilIcon`), update `components/auth/BrandMark.tsx` to the
new lockup, **delete `icons/PencilIcon.tsx`** (the last hard-coded-hex SVG in the app), regenerate
`src/app/favicon.ico` from the mark, and fix `public/site.webmanifest` — brand
`theme_color`/`background_color` (currently starter `#000000`/`#ffffff`) and icon entries that actually
exist (generate the referenced PNG sizes from the mark, or trim the manifest — today it points at missing
`img/favicon/*.png`).
### 3.6 Icon system — one family, working sizes, full vocabulary
- **Fix the size bug** in `AppIcon.tsx`: drive `fontSize` via `style`/`sx` instead of `width`/`height`
attributes (which MUI `SvgIcon`'s `1em` class CSS beats) — all ~40 existing `size={14..56}` call-sites
start working simultaneously. Stop spreading the invalid `size` attribute onto the DOM `<svg>`; make the
unknown-name `console.warn` **dev-only**; keep the custom-SVG path (the new logomark) scaling correctly.
- **Normalize the ~87-entry registry to ONE visual family** — recommend the **Rounded** variants of
`@mui/icons-material` (zero new deps, warmer than the current filled/outlined mix, fits
clinical-but-human). A `config.ts`-only sweep thanks to the registry indirection. Delete the eight dead
starter entries (`daynight`/`night`/`day`/`visibilityon`/`visibilityoff`/`signup`/`login`/`settings`
re-verify zero usages before each delete).
- **Add the missing vocabulary**: `back`/`chevron_start` (no directional nav glyph exists today except
`expand`), `share`, `copy`, `phone` (non-emergency call), `navigate` (directions), `sort`, `attachment`,
`star_half`. Note `camera`, `calendar`, `wallet`, and `tune` (filter) are **already registered** — they
only need family normalization. **Directional icons auto-mirror**: register `back`/`chevron_start` as the
LTR glyph, mirrored via a `[dir="rtl"]` `scaleX(-1)` rule applied by `AppIcon` for a declared
`DIRECTIONAL_ICONS` set in `config.ts`; write the rule into the registry comment and the skill — later
phases must not hand-roll flips.
### 3.7 AppButton de-startering
- Remove `DEFAULT_SX_VALUES` (`margin: 1`), then sweep the now-no-op **213 `sx={{ m: 0 }}` neutralizations
across 82 files** (mechanical: remove the key; drop `sx` when it becomes empty). Outer spacing becomes the
parent's job (Stack/Box gaps). Clean the starter prop cruft: duplicate `label`/`text` props (pick one,
migrate the loser's call-sites), the false "Box around to specify margins" JSDoc, the `// Missing props`
comment, and the `underline` spread onto non-link buttons (`AppButton.tsx:85`).
- **Do not break the public API**: `to`/`href` auto-link composition, icon-name `startIcon`/`endIcon`, and
non-MUI-color-becomes-text-color all stay (238 call-sites). Leave the `color='inherit'` default as-is —
changing every unspecified button's color is a per-screen decision for later phases. Update
`AppButton.test.tsx` (and every other touched shared component's test) in the same change.
### 3.8 Starter residue purge
- Delete `theme/light.ts` + `theme/dark.ts` and their exports from `theme/index.ts` (verified: nothing else
imports them; drop `LIGHT_THEME`/`DARK_THEME`/`APP_THEME`/`LIGHT_THEME as default` — keep `ThemeProvider`,
`getDirection`, `APP_THEME_LTR/RTL`). Delete the deprecated `TYPOGRAPHY` alias in `typography.ts` once
nothing imports it.
- Rewrite `app/globals.css` as an intentional minimal base: keep the `box-sizing` reset, **drop
`max-height: 100vh`** and the `max-width: 100vw; overflow-x: hidden` mask, add `::selection` in brand
colors, and a commented decision on the `a { color: inherit }` reset (AppLink owns link affordance).
- Fix the wrong `CONTENT_MIN_WIDTH` comment (`components/config.ts:5`), prune the starter
commented-out-alternatives style there into owned decisions, and delete the unused `AppImage` + its test +
barrel export (verified: zero product usages).
### 3.9 No-flash color-scheme boot
- A cookie-less OS-dark first visit paints light, then flips. Implement the **`ColorSchemeScript`** that
`client/CLAUDE.md`'s Theme System section already documents (acknowledged doc drift): an inline `<head>`
script in `app/[locale]/layout.tsx` that reads the `color-scheme` cookie, falls back to
`matchMedia('(prefers-color-scheme: dark)')` when absent, and sets `data-mui-color-scheme` before first
paint — plus the documented `Storage.prototype` patch. **Never** MUI's `InitColorSchemeScript` (banned in
`client/CLAUDE.md` — it reads localStorage, which diverges from the cookie). The script must agree with
`getThemeMode()` on every path. Switch `viewport.themeColor` (`layout.tsx:50-52`) to the media-query array
form: light → `BRAND.teal`, `(prefers-color-scheme: dark)``BRAND.tealDeep`, so browser chrome matches
the page in both schemes.
### 3.10 Keep the design contract honest
Update `.claude/skills/frontend-designer/SKILL.md` **in the same change**: the new tokens (elevation, radius,
motion, focus, rating, trust, money-emphasis) and when to use each; the type-scale + weight decision (its
"buttons weight 600" rule becomes wrong the moment 3.3 lands); the normalized icon family + directional-
mirroring rule + a corrected registered-icons note (it lists ~19 of ~87 icons and still names the dead
starter entries); and the brand-mark construction as the written source of truth now that
`product/balinyaar.html` is gone (fix its reference; `tokens.css`'s header points here too).
**(DEFERRED → phase 1):** all new shared primitives — EmptyState/ErrorState, PageHeader, `<Money>`, Jalali
picker, StatusChip v2, RatingInput's star fix, skeleton twins, route-level `loading.tsx`/`error.tsx`/404.
**(DEFERRED → phase 2):** the shells — TopBar/SideBar chrome, `UserInfo`, nav grouping, SSR mobile-first flash.
**(DEFERRED → phase 12):** the app-wide motion pass that consumes 3.4's motion tokens.
## 4. Mocks & seams in this phase
None. This phase is pure client theming — no service seams, no mock flags. The chain's REQ posture: if a
backend gap surfaces (none is expected here), append a REQ entry to
[../../shared-working-context/frontend/requests/for-backend.md](../../shared-working-context/frontend/requests/for-backend.md)
— REQ-001…038 are taken (verified: the tracker ends at REQ-038); number onward from **REQ-039**. UI stays
mock-tolerant behind the existing `services/{domain}` seams; never edit `server/`.
## 5. Critical rules you must not get wrong
- **The two-layer token sync is law** — every color added/changed lands in `tokens.css` (**both** scheme
blocks) *and* `colors.ts` in the same commit — and **zero hex regressions**: feature code has effectively
no hard-coded hexes; the theme pass must not reintroduce literals outside the theme layer (deleting
`PencilIcon` removes the last offender).
- **Do not break the RTL dual-theme/Emotion setup** (`APP_THEME_LTR`/`APP_THEME_RTL` stay module-load-built;
`createTheme()` never in a component; overrides use logical properties only) **or the cookie-SSR
color-scheme sync** (`colorSchemeSelector` stays the explicit `'data-mui-color-scheme'`, never `'data'`;
no `InitColorSchemeScript`; no `storageWindow`).
- **The AppIcon registry indirection stays** — keep the `<AppIcon icon="name">` API, the snake_case domain
names, the lowercase keys. **AppButton's composition API stays** (`to`/`href` → AppLink, icon-name
`startIcon`/`endIcon`) — 238 call-sites; the margin removal is a default change, not an API change.
- **Keep the keep-lists.** Notably: `textTransform: 'none'`, radius 10 as the house radius, the lifted
dark-teal palette, per-locale font loading (Mikhak never ships to `/en`; mirror that discipline if you
wire Space Grotesk), and `NotistackProvider` staying token-driven.
- **Design-contract non-negotiables:** any new user-facing string (e.g. the logo's aria/alt) goes in
**both** `messages/en.json` and `messages/fa.json`; MUI v9 API only; co-located `*.test.tsx` updated for
every touched shared component (`AppIcon`, `AppButton`, `BrandMark`, …); fetch/cookies rules untouched.
- **Update the skill and `client/CLAUDE.md` in the same change** (3.10 + §8) — stale design docs are how the
next phase reintroduces the starter.
## 6. Definition of Done
On top of the shared [definition-of-done.md](../../phases/_shared/definition-of-done.md):
- [ ] `npm run check` green; `npm run test:ci` green incl. updated tests for every touched shared component;
`en.json`/`fa.json` in sync.
- [ ] `theme.ts` has a `components` block covering at least the 3.1 list; no override uses a raw hex or a
physical direction property.
- [ ] `LIGHT_PALETTE`/`DARK_PALETTE` define success/error/warning/info; an inline
`<Alert severity="success">` and a success toast are visibly the same color family in both schemes.
- [ ] `TYPOGRAPHY_RTL` has `letterSpacing: 0` everywhere + the new line-heights and responsive heading
sizes; no `fontWeight: 600` remains in `src` (or a real 600 face is loaded — per the documented
decision); the Space Grotesk question is resolved (wired or deleted).
- [ ] `tokens.css` (both blocks) carries elevation, radius, motion, focus-ring, rating, money-emphasis, and
trust tokens, mirrored in `colors.ts` where palette-level.
- [ ] `ICONS.logo` is the new Balinyaar mark; `PencilIcon.tsx`, `light.ts`, `dark.ts`, `AppImage`, and the
eight dead icon entries are deleted; favicon + webmanifest rebuilt with brand colors and only real
files referenced.
- [ ] `<AppIcon icon="verified" size={48} />` actually renders 48px; the registry is one visual family;
`back`/`chevron_start` mirror correctly under `dir="rtl"`; `AppButton` has no default margin and
**zero** `sx={{ m: 0 }}` neutralizations remain in `src`.
- [ ] First visit with OS dark preference and no cookie paints dark with no light flash; browser
`theme-color` matches the scheme in both modes.
- [ ] Visual verification on the four axes — `/fa` + `/en` × light + dark — and mobile + desktop widths on
the §7 walk, with screenshots in the report; skill + `client/CLAUDE.md` updated to match reality.
## 7. How to test (what a human can verify after this phase)
1. Open `/fa/login` (private window, OS set to dark, no cookies): the page paints **dark immediately** — no
light flash — and shows the **new Balinyaar logomark** (not a pencil), recoloring correctly in dark mode.
2. On the customer home `/fa`: buttons are flat (no elevation) with comfortable padding; cards show hairline
borders + soft teal-tinted shadows (not grey); chips are soft-tinted; nothing renders stock-MUI grey/blue.
Tab through: every focusable element shows the same 2px brand focus ring.
3. Visit `/fa/nurse` and `/fa/admin` (seeded accounts): the top bar is a cream/paper surface with a hairline
divider — **not** a solid teal slab; sidebar icons are one visual family (all Rounded); the selected nav
item uses the soft-primary fill.
4. Trigger an inline alert (e.g. a form error state) and a toast on the same screen: both use the same
brand-harmonized semantic colors, in light and dark.
5. On `/fa`, inspect Persian text: no letter-spacing gaps inside joined words, headings don't clip
ascenders, bold shows a real weight hierarchy (not everything Bold). Compare `/en`: headings render per
the 3.3 decision (Space Grotesk or the documented system stack).
6. Check the browser tab: the favicon is the new mark; toggling OS dark mode flips the browser chrome
(`theme-color`) to the deep-teal value.
## 8. Hand off & document (close the phase)
- Update `client/CLAUDE.md`: the Theme System section (`ColorSchemeScript` now real — remove the doc-drift
note; new tokens; the weight decision), the theme entries in "Project Structure" (`light.ts`/`dark.ts`
removed, new icon assets), and the Fonts table if Space Grotesk was wired. Update
`.claude/skills/frontend-designer/SKILL.md` per 3.10 (part of the phase, not optional).
- Write the frontend report at `dev/shared-working-context/reports/ui-phase-0-report.md`: what changed at the
theme layer, the type-scale + weight + icon-family decisions, before/after screenshots on the four axes,
what later phases must know (new token names, directional-icon rule), and any REQs filed (expected: none).
- Save a memory note per operating-rules §8: the theme/token/icon/brand decisions, the size-bug fix, the
margin-default removal, and the no-flash boot mechanism — later UI phases build on all of them.