some ui phase improvement planning
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
# UI Phase 1 — Shared primitives & app-wide states
|
||||
|
||||
> **Mission:** build the shared component kit the pages keep hand-rolling — state views, page header, confirm
|
||||
> dialog, card anatomy, `<Money>`, locale formatting, a Jalali date picker, status/timeline/countdown/rating
|
||||
> upgrades, skeleton twins — give the app real route-level chrome (loading / error / 404 / per-route metadata),
|
||||
> and fix the error→false-empty defects the missing kit caused. This phase **owns every shared primitive**
|
||||
> (see the [README ownership rules](README.md)); phases 3–11 only consume what ships here.
|
||||
>
|
||||
> **Track:** frontend · **Depends on:** [Phase 0](ui-phase-0-design-language.md) (theme pass, icon family,
|
||||
> `--bal-money-emphasis` / `--bal-rating*` / elevation / motion / focus tokens) · **Unlocks:** the kit every
|
||||
> area redesign (phases 3–11) composes from
|
||||
> **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 disciplined (tokens, RTL, four-state data pattern), but the *page primitives*
|
||||
were only ever built for the backoffice. Everything user-facing is hand-rolled per page, so the app reads as
|
||||
a starter and every restyle is a 20-file change. Verified in code:
|
||||
|
||||
- **29 hand-rolled dashed-border `Paper` empty/error blocks across 23 files** (grep `border: '1px dashed'`),
|
||||
while `AdminEmptyState`/`AdminErrorState` sit unused outside `components/admin/`.
|
||||
- **24 hand-assembled `formatIrrToToman(x) + t('currency_toman')` sites across 19 files**; `PriceBreakdown.tsx:62`
|
||||
colors the grand total terracotta (`--bal-secondary`, ≈2.7:1 on white — a WCAG failure on the most
|
||||
trust-critical number), a pattern repeated across five sibling money components.
|
||||
- **23 copies of the `locale === 'fa' ? 'fa-IR' : 'en-US'` ternary across 15 files**, already drifting
|
||||
(`admin/partners/[id]/page.tsx` passes the raw locale); **no relative-time formatting anywhere**; the OTP
|
||||
resend clock renders Latin digits (`components/auth/OtpStep.tsx:27`).
|
||||
- **Zero `loading.tsx` / `error.tsx` / `not-found.tsx` / `global-error.tsx`** under `client/src/app`; the only
|
||||
crash UI is the starter `ErrorBoundary` (English `<h2>`, raw `componentStack` dump); one static `<title>`
|
||||
serves ~60 routes; **no Jalali date input** — every date input is a native Gregorian `type="date"`.
|
||||
- **The missing kit caused real defects.** A failed query renders as *success-with-no-data*:
|
||||
- `(customer)/page.tsx:57–68` — the page gate destructures only `data` from `usePatients()`; on error it
|
||||
returns `<AppLoading />` forever (spinner-hang). The `CategoryGrid` in the same file branches all four
|
||||
states correctly — the discipline exists, the gate doesn't.
|
||||
- `(customer)/patients/page.tsx:83` (`isEmpty = !isLoading && …length === 0` → error shows «no patients»);
|
||||
`nurse/requests/page.tsx:19` (error = empty inbox); `nurse/services/MyServicesList.tsx:37,42` (error =
|
||||
«add your first service» CTA).
|
||||
- `(customer)/profile/page.tsx:16` — an errored profile renders a **blank form** whose save could overwrite
|
||||
server truth; `nurse/services/VariantBuilder.tsx:384` — `optionGroupsQuery` has no error branch, so failed
|
||||
option groups are treated as "category has none", letting a nurse skip required options.
|
||||
- **Silent mutations:** `nurse/profile/page.tsx:44` (avatar upload) and `:54–64` (profile save) pass only
|
||||
`onSuccess` — a failure gives zero feedback.
|
||||
|
||||
**What already exists (do not rebuild):**
|
||||
|
||||
- The admin primitives to *promote*, not rewrite (all in `client/src/components/admin/`, all tested):
|
||||
`AdminPageHeader` (title/subtitle/actions, caller-owned i18n), `AdminEmptyState`/`AdminErrorState`,
|
||||
`ConfirmDialog` (required-reason gating + busy-disable).
|
||||
- `utils/money.ts` (BigInt IRR↔Toman, fa digits) and `utils/date.ts` (Shamsi via `Intl` `fa-IR-u-ca-persian`,
|
||||
no date library); `CountdownTimer`'s architecture — server-frozen UTC deadline prop, self-owned 1s tick
|
||||
(only it re-renders), single `onElapsed` fire, `dir="ltr"` tabular-nums locale digits (`CountdownTimer.tsx:84`).
|
||||
- `StatusChip` as the single status color+icon source seven components delegate to; the exhaustive typed
|
||||
enum→chip mappings; `TrustBadge`'s honest three states; the four-state pattern itself (~95 `isError`
|
||||
branches, ~124 retries) — restyle and fill gaps, don't rebuild; the `dispatchToast → notistack` pipeline.
|
||||
- Phase 0's output: `theme.components` pass, one icon family, the new tokens. Build on them.
|
||||
|
||||
## 2. Required reading (do this first)
|
||||
|
||||
- [audit/component-primitives.md](audit/component-primitives.md) (kit inventory, hand-roll counts),
|
||||
[audit/feature-components.md](audit/feature-components.md) (card-anatomy drift, rating/terracotta/timeline
|
||||
defects), [audit/cross-cutting-ux.md](audit/cross-cutting-ux.md) (route chrome / metadata / formatting) —
|
||||
each carries the full evidence and a keep-list.
|
||||
- [.claude/skills/frontend-designer/SKILL.md](../../../.claude/skills/frontend-designer/SKILL.md) — the design
|
||||
contract; and `client/CLAUDE.md` — Golden rules, Project Structure, Unit Testing, Toast Notifications, i18n.
|
||||
- Code to read before writing: `client/src/components/admin/{AdminPageHeader,AdminEmptyState,AdminErrorState,ConfirmDialog}.tsx`;
|
||||
`client/src/components/{StatusChip,CountdownTimer,RatingInput,StepperHeader,RefundStatusCard,PriceBreakdown}/`;
|
||||
`client/src/components/booking/BookingStatusTimeline/`; `client/src/utils/{money,date}.ts`;
|
||||
`client/src/lib/api/client.ts` (which errors the fetch layer already toasts — 3.14 builds on it);
|
||||
`client/src/theme/tokens.css` as phase 0 left it. Product framing: `product/overview/platform-summary.md`
|
||||
(money display and Shamsi correctness are product rules, not preferences).
|
||||
|
||||
## 3. Scope — build this
|
||||
|
||||
New shared components live under `client/src/components/common/<Name>/` (component + `index.tsx` barrel +
|
||||
co-located `.test.tsx`), exported from `@/components`, presentational with **caller-owned i18n** (already-
|
||||
translated strings in, like `AdminPageHeader` documents). Strings a component *does* own (route chrome,
|
||||
default retry labels) go in **both** `messages/en.json` and `messages/fa.json`.
|
||||
|
||||
### 3.1 State kit — `EmptyState`, `ErrorState`, `QueryStateGate` + the false-empty fixes
|
||||
|
||||
- Promote/generalize `AdminEmptyState`/`AdminErrorState` into shared `EmptyState` and `ErrorState` with a
|
||||
branded look (phase 0 surface/radius tokens; registry icon slot; calm — retire the dashed border).
|
||||
`EmptyState`: icon, title, body, optional CTA. `ErrorState`: **`onRetry` is required** («تلاش دوباره»).
|
||||
`components/admin` keeps thin wrappers or its call sites migrate — either way no duplicated implementation
|
||||
remains. (Custom illustration set: DEFERRED → phase 12.)
|
||||
- Add a small `QueryStateGate` (props: `isLoading`, `isError`, `onRetry`, `isEmpty`, `skeleton`, `empty`,
|
||||
`children`) rendering skeleton → error → empty → data in that order, **plus** write the convention into
|
||||
`client/CLAUDE.md`: *an errored query must never render as empty; every error branch has a retry.* Pages
|
||||
with naturally inline branching (infinite lists) may follow the convention without the component.
|
||||
- Fix the six defects from §1 with it. The profile fix must render `ErrorState` instead of the blank form;
|
||||
VariantBuilder's option-group error must block progression, not pretend "no options".
|
||||
- Mechanical sweep: replace the 29 dashed-`Paper` blocks in 23 files with `EmptyState`/`ErrorState`. Copy
|
||||
stays per-page (existing keys); only the shell unifies.
|
||||
|
||||
### 3.2 `PageHeader` + `ConfirmDialog` promotions
|
||||
|
||||
- Generalize `AdminPageHeader` into a shared `PageHeader`: `title`, `subtitle?`, `actions?`, plus a `backTo?`
|
||||
affordance (RTL-flippable chevron via the phase 0 icon family; if phase 0 shipped no `back` icon, extend
|
||||
`AppIcon/config.ts` minimally and note it — ownership rule). Adopt it **mechanically** where pages already
|
||||
hand-roll the identical h5/h1 + subtitle block (patients, addresses, bookings, earnings, …); per-page header
|
||||
design is each area phase's job (DEFERRED → 3–11). `AdminPageHeader` becomes a re-export or its call sites migrate.
|
||||
- Move `components/admin/ConfirmDialog` to `components/common/ConfirmDialog` **preserving its contract**:
|
||||
required-reason gating, busy state disabling both buttons, caller-owns-the-mutation. Migrate the admin
|
||||
imports and the hand-rolled confirm flows only where mechanical (patients archive, addresses delete); the
|
||||
remaining raw `<Dialog>` confirm sites are area-phase work (DEFERRED → 3–11).
|
||||
|
||||
### 3.3 Card kit — `SurfaceCard` + `AccentCard`
|
||||
|
||||
Encode the de-facto anatomy once: `SurfaceCard` = flat `Paper` (`elevation={0}`), 1px `divider` border, house
|
||||
radius, a padding scale (`sm`/`md`/`lg` ≈ today's p:2 / 2.5 / 3); `AccentCard` = `SurfaceCard` + a
|
||||
`borderInlineStart` accent taking a **semantic token name**, at one standardized width (pick one; end the
|
||||
3px/4px drift). Migrate the ~12 hand-rollers mechanically — `EarningsRow`, `EarningsBalanceHeader`,
|
||||
`PayoutHistoryRow`, `PatientCard`, `VariantCard`, `VisitNoteCard`, `InstallmentScheduleRow`, `PriceBreakdown`,
|
||||
`BankStatusPanel`, `DocumentUpload`'s panels, `BookingRequestSummaryCard`, the customer-home `NudgeCard` —
|
||||
visual unification only, zero content/behavior change.
|
||||
|
||||
### 3.4 `<Money>` primitive
|
||||
|
||||
One component for every displayed amount: takes a **served IRR digit-string**, renders Toman + unit label via
|
||||
`utils/money.ts` (never computes); `size`/`tone` variants with emphasis driven by `--bal-money-emphasis`
|
||||
(phase 0) — **not terracotta**; deduction rendering (muted/error tone with an explicit visual sign treatment,
|
||||
never a bare minus inside RTL text); optional strikethrough. Replace the 24 hand-assembled sites in 19 files —
|
||||
this fixes the `PriceBreakdown` total-contrast defect and the sibling terracotta-money sites (`RefundStatusCard`,
|
||||
`BnplPlanCard`, `InstallmentScheduleRow`, `CancellationPolicyDisclosure`) in one pass. `PriceDisplay` (catalog
|
||||
unit rates) stays; refactor it onto `<Money>` internals only if trivial — its contract is tested and correct.
|
||||
|
||||
### 3.5 Formatting utils — `utils/number.ts`
|
||||
|
||||
- `localeTag(locale)` → `'fa-IR' | 'en-US'` and `formatNumber(value, locale, options?)` — kill the 23
|
||||
copy-pasted ternaries in 15 files (including the drifted `admin/partners/[id]/page.tsx` raw-locale call).
|
||||
- `formatRelativeTime(iso, locale)` via `Intl.RelativeTimeFormat`; **rule (document in JSDoc): relative up to
|
||||
~7 days («۲ ساعت پیش»، «دیروز»), then decay to the absolute Shamsi date** via `formatShamsiDate`. Consumers
|
||||
land in phases 10/11; ship the util + tests now.
|
||||
- `formatClock(totalSeconds, locale)` — locale-digit `mm:ss`/`hh:mm:ss`; migrate `CountdownTimer`'s inline
|
||||
`Intl` pad onto it and fix `OtpStep.tsx:27`'s Latin-digit resend clock.
|
||||
|
||||
### 3.6 `JalaliDatePicker` + `JalaliDateField`
|
||||
|
||||
Shamsi-native date selection that **emits ISO Gregorian** (the wire stays Gregorian; display stays Shamsi):
|
||||
a month-grid calendar (Persian month/weekday names, today marked, min/max props), a compact **day-chip-strip**
|
||||
variant for near dates (next N days as tappable chips — the C4 booking form's shape), and `JalaliDateField`
|
||||
(read-only input + picker popover) to replace native `type="date"` inputs. **Investigate and DECIDE the
|
||||
arithmetic layer:** prefer `Intl` `fa-IR-u-ca-persian` (already proven in `utils/date.ts`) for display + a
|
||||
minimal conversion for month math; a tiny, well-maintained jalali lib (e.g. `jalaali-js`, ~2 KB) is acceptable
|
||||
if `Intl`-only arithmetic gets awkward — document the choice and measured bundle cost in the report; no
|
||||
heavyweight date library. Locale-aware (`fa` Persian calendar, `en` may show Gregorian — same ISO output),
|
||||
keyboard-navigable, RTL-correct, both schemes. Consumed by phases 4, 5, 8, 11 — consumer migration is theirs
|
||||
(DEFERRED → 4/5/8/11).
|
||||
|
||||
### 3.7 `StatusChip` v2
|
||||
|
||||
Soft-tint default: `-soft` token backgrounds + strong text (extend `tokens.css` + `colors.ts` with any missing
|
||||
`-soft` pairs, both schemes); solid fill reserved for high-alarm states; a documented status weight hierarchy
|
||||
(neutral < info < progress < success < warning < alarm). `StatusChip` is the delegation point, so this is
|
||||
mostly one file; update direct chip consumers mechanically and keep the exhaustive enum→chip mappings intact.
|
||||
|
||||
### 3.8 `StatusTimeline`
|
||||
|
||||
A designed vertical timeline: node = label + optional timestamp + optional note; states completed / current
|
||||
(animated within the phase 0 motion tokens, reduced-motion-safe) / pending / terminal-failure. Migrate the
|
||||
two StepperHeader-as-status-display misuses: `RefundStatusCard.tsx:76` (3-step refund progress) and
|
||||
`components/booking/BookingStatusTimeline` (7-status booking lifecycle). **StepperHeader remains for real
|
||||
wizards only** (onboarding, verification, BNPL) — write that into its JSDoc.
|
||||
|
||||
### 3.9 `CountdownTimer` v2
|
||||
|
||||
Keep the do-not-regress architecture (server-frozen deadline, isolated tick, single `onElapsed`, `dir="ltr"`
|
||||
tabular digits). Add: a **progress-ring variant** (given `windowStart`); **urgency tiers** — calm teal →
|
||||
amber → terracotta — with threshold props, replacing the binary `urgent`; **humanized coarse mode** above
|
||||
~10 minutes («۲۵ دقیقه مانده», minute-grain updates) switching to the ticking clock below it; keep the `label`
|
||||
slot; `aria-live="polite"` at coarse intervals (never per second). Swap the hourglass `pending` glyph for the
|
||||
clock; drop the hard-coded `1.5rem` for size variants. Existing consumers (C5 tracker, nurse inbox, checkout,
|
||||
EarningsRow dispute window) keep working unchanged — new props optional.
|
||||
|
||||
### 3.10 `RatingInput` v2
|
||||
|
||||
Switch fill colors to phase 0's `--bal-rating` / `--bal-rating-empty`; **fractional fill** (dual-layer or clip)
|
||||
for read-only averages so 4.5 renders as 4.5 — then remove the `Math.round` workaround at
|
||||
`search/nurse/[nurseId]/page.tsx:260` (it shows a 4.5 average as 5 stars — an honesty bug on the core trust
|
||||
surface); size variants; keep the radiogroup/readOnly `role="img"` semantics and `data-*` hooks. Swap the star
|
||||
color in `NurseResultCard` and `BookingRequestSummaryCard` to the same tokens.
|
||||
|
||||
### 3.11 Skeleton twins + `AppLoading` rebrand
|
||||
|
||||
Co-located `<X.Skeleton />` statics matching real anatomy (avatar disc, chip row, price line) for
|
||||
`NurseResultCard`, `EarningsRow`, `PayoutHistoryRow`, `PatientCard`, `VisitNoteCard`, `TicketListCard`; adopt
|
||||
at the loading branches that hand-guess heights (e.g. `search/results/page.tsx:75`). Rebrand `AppLoading` into
|
||||
a branded splash (brand mark + subtle motion, reduced-motion-safe) used **only** for true full-page waits
|
||||
(auth splash, gateway return). Of the 18 screens using it today, switch the ones with a shaped skeleton
|
||||
available to skeletons; the rest keep the splash until their area phase.
|
||||
|
||||
### 3.12 Route chrome — loading / error / 404
|
||||
|
||||
Per-route-group `loading.tsx` with shell-shaped skeletons (`(customer)` header + card stack; `nurse`/`admin`/
|
||||
`partner` sidebar-shell content; `(public-routes)` auth card). Branded `error.tsx` (per `[locale]` segment)
|
||||
and `not-found.tsx` + the next-intl catch-all 404 pattern — calm Persian-first copy, brand mark, retry
|
||||
(`reset()`) and «بازگشت به خانه» CTAs, keys in both catalogs. `global-error.tsx` (sits above `[locale]`,
|
||||
renders its own `<html>` — it cannot use next-intl): minimal static **bilingual** fa+en copy — the one
|
||||
sanctioned hard-coded-string exception; note it in `client/CLAUDE.md`. Rewrite `components/common/ErrorBoundary`
|
||||
to the same design: i18n'd, branded, retry affordance, **stack/componentStack rendered only in development**
|
||||
(logged, never shown, in prod).
|
||||
|
||||
### 3.13 Per-route metadata
|
||||
|
||||
Title template via one `generateMetadata` in `app/[locale]/layout.tsx`: `'%s | بالینیار'` (fa) /
|
||||
`'%s | Balinyaar'` (en) + localized description. Establish and document the client-page pattern — `page.tsx`
|
||||
becomes a thin RSC exporting `generateMetadata` and rendering the co-located `'use client'` body — and apply
|
||||
it to the main landing pages now (customer home, `/login`, `/search`, `/bookings`, `/nurse`, `/admin`,
|
||||
`/partner`). Full per-page adoption continues in the area phases (DEFERRED → 3–11).
|
||||
|
||||
### 3.14 Mutation-error convention
|
||||
|
||||
Read `lib/api/client.ts` to confirm exactly which failures the fetch layer already toasts; then: **every
|
||||
mutation whose failure is not surfaced inline or by the fetch layer gets an `onError` toast.** Fix the two
|
||||
known silent ones — nurse profile save (`nurse/profile/page.tsx:54–64`) and avatar upload (`:44`) — and write
|
||||
the convention into `client/CLAUDE.md` (Toast Notifications section).
|
||||
|
||||
## 4. Mocks & seams in this phase
|
||||
|
||||
None — pure UI over existing `services/{domain}` seams; no new service, mock, or flag. Expected REQ count:
|
||||
zero. If a genuine backend gap surfaces anyway, append it to
|
||||
[../../shared-working-context/frontend/requests/for-backend.md](../../shared-working-context/frontend/requests/for-backend.md)
|
||||
numbered **REQ-039 onward** (001–038 are taken), keep the UI mock-tolerant behind the seams, never touch `server/`.
|
||||
|
||||
## 5. Critical rules you must not get wrong
|
||||
|
||||
1. **Presentational purity + caller-owned i18n stays.** Shared primitives take already-translated strings and
|
||||
stable codes; no data-fetching, no wire-derived labels inside this layer.
|
||||
2. **Money invariants stay.** Amounts are served IRR digit-strings through `utils/money.ts` (BigInt); `<Money>`
|
||||
formats, never computes; `PriceBreakdown`'s dev reconciliation guard survives the card-kit migration.
|
||||
3. **CountdownTimer architecture stays** — server-frozen deadline, isolated self-tick, single `onElapsed`;
|
||||
deadlines never recomputed client-side. **ConfirmDialog's contract stays** through the move. **An error is
|
||||
never an empty** — the defect class this phase kills; every error state keeps a retry.
|
||||
4. **Don't break what delegates.** `StatusChip` is the single status source and the enum→chip mappings are
|
||||
exhaustively typed — keep both; `StepperHeader` keeps serving the real wizards untouched; honest-copy
|
||||
components (EscrowNotice verbatim fa, RefundStatusCard's no-success-framing-on-failure,
|
||||
EarningsBalanceHeader's "owed back") keep their copy and state logic — restyle shells only.
|
||||
5. **Design contract non-negotiables:** tokens not hexes (extend `tokens.css` + `colors.ts` together, both
|
||||
schemes); logical/RTL-safe props only (keep the deliberate `dir="ltr"` islands for clocks/IBANs/phones);
|
||||
both message catalogs in sync; MUI v9 API only; co-located tests for every shared component;
|
||||
fetch/cookies/toast rules per `client/CLAUDE.md` untouched.
|
||||
6. **Never add a layout above `[locale]`** — `global-error.tsx` is a special file, not a layout, and is the
|
||||
only file allowed static copy.
|
||||
|
||||
## 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 (many shared components are touched — run it); every new
|
||||
shared component has a co-located `.test.tsx`; `en.json`/`fa.json` in sync.
|
||||
- [ ] All six error→false-empty/spinner defects from §1 render `ErrorState` with a working retry (verify by
|
||||
forcing the query to fail); the profile error never shows an editable blank form.
|
||||
- [ ] Sweep greps clean: `border: '1px dashed'` under `client/src/app` → zero; `currency_toman` outside
|
||||
`utils/money.ts` + `<Money>`/`PriceDisplay` internals → zero; `locale === 'fa' ? 'fa-IR'` → only
|
||||
`utils/number.ts`. The checkout total renders in `--bal-money-emphasis`, not terracotta.
|
||||
- [ ] `loading.tsx`, `error.tsx`, `not-found.tsx`, `global-error.tsx` exist and render branded, localized
|
||||
(404/error verified on `/fa` and `/en`); ErrorBoundary shows no stack in a production build; browser
|
||||
tab titles differ per section using the `%s | بالینیار` template.
|
||||
- [ ] `JalaliDatePicker`/`JalaliDateField` render Shamsi, emit ISO Gregorian, pass keyboard/RTL tests; the
|
||||
arithmetic decision + bundle cost is documented in the report.
|
||||
- [ ] A 4.5 rating average renders a half-filled fifth star on the nurse profile (no `Math.round`);
|
||||
`RefundStatusCard` and the booking timeline render `StatusTimeline` (wizards still use `StepperHeader`).
|
||||
- [ ] Nurse profile save and avatar upload toast on failure; the convention is in `client/CLAUDE.md`.
|
||||
- [ ] Visual verification on the four axes — `/fa` + `/en` × light + dark — and mobile + desktop for the state
|
||||
kit, card kit, route chrome, and the Jalali picker.
|
||||
|
||||
## 7. How to test (what a human can verify after this phase)
|
||||
|
||||
1. Stop the backend (or force queries to reject via devtools offline mode) and load `/fa`: customer home shows
|
||||
a branded error state with «تلاش دوباره» — not an infinite spinner. Restore, retry → home renders. Repeat on
|
||||
`/patients`, `/profile`, `/nurse/requests`, `/nurse/services`: an error state, never «empty» copy or a blank form.
|
||||
2. Visit a garbage URL (`/fa/xyz`) → branded Persian 404 with a home CTA. Throw inside a page in dev → branded
|
||||
error screen with retry; build production and confirm no stack is rendered.
|
||||
3. Navigate between sections and watch the tab title change («جستجو | بالینیار», «رزروها | بالینیار», …).
|
||||
4. Open search results while loading: skeletons match the card anatomy — no layout jump when data lands. Open
|
||||
checkout (mock path): the total renders in the money-emphasis color with correct contrast in both schemes;
|
||||
a deduction row (earnings commission) reads visually as a deduction.
|
||||
5. Open the C5 request tracker: progress ring, coarse «X دقیقه مانده» above 10 minutes, ticking Persian-digit
|
||||
clock below, escalating tint as thresholds pass; the `/login` OTP resend clock shows Persian digits on `/fa`.
|
||||
6. Open a nurse profile with a fractional average: stars render the fraction. Open a refund status page:
|
||||
vertical timeline with node states, not a form stepper.
|
||||
7. Mount `JalaliDateField` (the phase's test page or the first consumer): pick «۱۵ مرداد» → the emitted value
|
||||
is the correct ISO Gregorian date; keyboard navigation works; `/en` shows the Gregorian view.
|
||||
8. Kill the network mid-save on the nurse profile → an error toast appears; the form keeps the draft.
|
||||
|
||||
## 8. Hand off & document (close the phase)
|
||||
|
||||
- Update `client/CLAUDE.md`: Project Structure (new `components/common/*` primitives, route-chrome files,
|
||||
`utils/number.ts`), the QueryStateGate/error-never-empty convention, the mutation-error toast convention,
|
||||
the metadata pattern, and the `global-error.tsx` exception.
|
||||
- Write the report at `dev/shared-working-context/reports/ui-phase-1-report.md`: what shipped, the Jalali
|
||||
decision + bundle cost, icon-registry additions (if any), the sweep counts, any REQ entries appended to
|
||||
[for-backend.md](../../shared-working-context/frontend/requests/for-backend.md) (REQ-039+; expected none),
|
||||
and any foundation gaps left for phase 2+.
|
||||
- Save a memory note per operating-rules §8: the primitives kit now exists and later phases must consume it
|
||||
(never fork local variants), the error-never-empty and mutation-toast conventions, and the Jalali decision.
|
||||
Reference in New Issue
Block a user