some ui phase improvement planning

This commit is contained in:
hamid
2026-07-17 13:22:04 +03:30
parent 70fb0a9202
commit 9051bb3e18
41 changed files with 6291 additions and 5 deletions
@@ -0,0 +1,313 @@
# 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.