314 lines
24 KiB
Markdown
314 lines
24 KiB
Markdown
# 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.4–1.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** — 2–3 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.
|