ui phase 12
This commit is contained in:
@@ -1034,7 +1034,28 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
|
||||
primary, `USE_PARTNER_MOCK`); the real client call is written but unreachable until this route exists.
|
||||
- **Status:** open
|
||||
|
||||
## REQ-061 — Admin user lookup: name/phone search + batch id→label resolve — filed by ui-phase-11 — 2026-07-19
|
||||
## REQ-065 — Public/authenticated policy-config read (dispute window, cancellation tiers, refund ETA) — filed by ui-phase-12 — 2026-07-19
|
||||
- **Need:** A public or any-authenticated-caller read exposing the legally/financially sensitive policy
|
||||
numbers that trust-critical copy states in plain language: the nurse-payout dispute-window hours
|
||||
(currently 72), the cancellation lead-time tier boundary hours (currently 24), and the BNPL-revert
|
||||
refund ETA business-day range (currently 7–10). Proposed: `GET api/v1/platform_config/public` (or a
|
||||
narrower `.../policy_summary`) → `{ disputeWindowHours, cancellationLeadHours,
|
||||
refundEtaMinBusinessDays, refundEtaMaxBusinessDays }` — a read-only projection of the existing
|
||||
admin-only `platform_config` rows (`cfg_group_deadlines`/`cfg_group_cancellation`), not a new config
|
||||
system.
|
||||
- **Why:** §3.6 of the phase-12 copy sweep parameterized `payouts.explainer_point_2`,
|
||||
`refunds.lead_gt_24h`/`lead_lt_24h`, and `refunds.eta_business_days` to take `{hours}`/`{minDays}`/
|
||||
`{maxDays}` instead of hard-coding the numbers into the string — but at authoring time only the
|
||||
**admin-scoped** `platform_config/*` routes exist (verified against `dev/contracts/domains/`), so a
|
||||
non-admin nurse/customer client has nothing to read these numbers from. Until this lands, the numbers
|
||||
are single-sourced in `client/src/constants/policy.ts` (`DISPUTE_WINDOW_HOURS`,
|
||||
`CANCELLATION_LEAD_HOURS`, `REFUND_ETA_MIN_BUSINESS_DAYS`, `REFUND_ETA_MAX_BUSINESS_DAYS`) and fed into
|
||||
the three call sites (`CancellationPolicyDisclosure`, `RefundEtaBanner`, the nurse earnings
|
||||
`ExplainerCard`) as ICU params — a config edit today still requires a client deploy to stay honest,
|
||||
but at least the numbers live in exactly one file with a comment pointing at this REQ, not scattered
|
||||
across message strings.
|
||||
- **Proposed shape:** as above; every field a plain number matching the admin config's current value.
|
||||
- **Status:** open
|
||||
- **Need:** Two endpoints backing a directory of platform users for admin use:
|
||||
1. `GET admin_users/search?q=&role=` — search by name/phone (min 2 chars), optionally narrowed to one
|
||||
coarse role (`customer`/`nurse`/`admin`/`partner`), returning up to ~10 matches: `{ id, displayName,
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# UI Phase 12 — Copy, motion & final polish — Report (2026-07-19)
|
||||
|
||||
This is the closing phase of the UI build chain (phases 0–12). Scope: a checked-in Persian style guide
|
||||
+ lint, the verified copy defects from the microcopy/cross-cutting audits, ICU plurals, arrows out of
|
||||
strings, a trust-copy pass, config-served policy numbers, a restrained app-wide motion language behind a
|
||||
single reduced-motion gate, an a11y sweep, two desktop-aware layouts, and this closing QA pass.
|
||||
|
||||
## What was built
|
||||
|
||||
### 3.1 — Persian style guide + enforcement
|
||||
- `client/messages/STYLE.md` — the binding one-pager: brand spelling (ZWNJ), تأیید hamza, one جستجو
|
||||
form, ZWNJ rules, punctuation, domain glossary (بیمار not مددجو), the shell-naming system
|
||||
(اپلیکیشن for end-user shells, کنسول for back-office), the verification-pipeline-vs-KYC-step naming
|
||||
split, a nurse-facing/admin-facing status-vocabulary rule, digits policy, register, and the
|
||||
policy-number-interpolation rule.
|
||||
- `client/scripts/check-copy.mjs` — a ~100-line dependency-free Node script that flattens `fa.json` and
|
||||
greps every leaf string against 5 banned-variant rules (plain-space brand, hamza-less تایید, جستوجو/جست
|
||||
و جو, the «بازی» word-boundary trap, the archaic میگردد passive — with a leading-space anchor so the
|
||||
legitimate verb «برمیگردد» is never a false positive). Wired as `npm run lint:copy`, folded into
|
||||
`npm run check`. Currently: **1919 strings checked, 0 banned variants.**
|
||||
|
||||
### 3.2/hamza/جستجو/میگردد sweep — ~90 keys swept across `fa.json`
|
||||
- Brand name: 5 keys (`common.brand`, `auth.customer_title`/`select_role_title`/`account_error_body`,
|
||||
`verification.start_body`) — plain-space → ZWNJ.
|
||||
- تأیید hamza: ~65 occurrences normalized to the hamza form across `home`, `profile`, `nurseProfile`,
|
||||
`bank`, `activation`, `search`, `booking`, `payment`, `auth`, `verification`, `refunds`, `bnpl`,
|
||||
`payouts`, `admin`, `partner`, `legal`. `bank.status_verified_chip`'s typo («تاییدشد» missing the final
|
||||
ه) is fixed to «تأییدشده» in the same edit.
|
||||
- جستجو: 3 keys folded (`coverage.empty_warning`, `booking.missing_nurse_body`, one `legal` section body).
|
||||
- Archaic میگردد → میشود: `refunds.confirm_restate`, `admin.mod_confirm_publish`,
|
||||
`admin.cfg_save_confirm_body` (3 occurrences, matching the audit's own recount, not the "1" a naive grep
|
||||
would find).
|
||||
- Grammar bugs: `booking.evv_no_open_check_in` («ورود بازی» → «ورودِ ثبتشدهای برای این ویزیت وجود
|
||||
ندارد؛ ابتدا ورود را ثبت کنید.») and `admin.alert_empty` («هشدار بازی» → «هشداری برای رسیدگی نیست.»).
|
||||
- `tickets.thread_empty_body` comma splice → two sentences.
|
||||
- `address.line_hint` — **already fixed by ui-phase-9** (verified in its report); not re-touched.
|
||||
- EVV introduced once: `booking.evv_visits_subtitle` now spells out «ثبت حضور الکترونیکی (EVV) — ورود و
|
||||
خروج شما ثبت میشود تا ویزیت بدون اختلاف تأیید شود» (this subtitle renders once per visits-page
|
||||
load); the short chips (`evv_check_in`/`evv_check_out`) stay abbreviated.
|
||||
- en: `auth.nurse_subtitle` "licence" → "license"; 6 curly-apostrophe holdouts in the `admin` namespace
|
||||
(`no_permission`, `doc_error`, `payout_run_confirm_body`, `mod_confirm_publish`, `access_denied`,
|
||||
`invoice_pdf_error`) → straight quotes. Zero curly quotes remain (verified by grep).
|
||||
|
||||
### Shell naming + verification-pipeline naming (fa only — en had no overload)
|
||||
- `shell.nurse_app`/`switch_to_nurse`: «نمای پرستار» → «اپلیکیشن پرستار» (end-user shells share one
|
||||
metaphor); `shell.partner_console`: «پرتال همکار» → «کنسول همکار» (back-office shells share the other).
|
||||
`booking.evv_nurse_view` ("نمای پرستار" as a perspective chip label, not a shell name) is untouched —
|
||||
it's a different concept and STYLE.md calls out the distinction explicitly.
|
||||
- «احراز هویت» renamed to «تأیید صلاحیت» for the **pipeline** (11 keys: `nav.verification`,
|
||||
`nurseProfile.unverified_body`/`unverified_cta`, `verification.title`/`load_error`/`start_title`/
|
||||
`start_cta`/`progress_title`/`approved_title`/`credentials_needs_start`/`review_approved_title`,
|
||||
`admin.ver_title`/`ver_case_title`); `verification.explainer_not_verified` rephrased to «صلاحیت این
|
||||
پرستار هنوز تأیید نشده است.» for the same reason. The **KYC step** keeps «احراز هویت» everywhere
|
||||
(`verification.step_identity_kyc`, `admin.step_identity_kyc`, `activation.row_identity`) — the two no
|
||||
longer share a name, so a nurse who passed KYC no longer sees the pipeline nav item read as
|
||||
contradictorily incomplete under the same word.
|
||||
- Status vocabulary: `admin.step_failed` («ناموفق» → «ردشده») now matches admin's own
|
||||
`agg_rejected`/`rstatus_rejected`/`mstatus_rejected` pattern (all «ردشده»); nurse-facing
|
||||
`verification.status_failed` keeps «رد شد» (STYLE.md §9 — one nurse-facing form, one admin-facing
|
||||
form). Money-failure vocabulary (`ناموفق` for payouts/refunds/batches) is untouched — a payment
|
||||
*failing* is a different concept from a document being *rejected*.
|
||||
|
||||
### 3.3 — ICU plurals with designed `=0` cases
|
||||
- fa gained ICU plural + a real zero-state on the 4 keys the audit flagged:
|
||||
`search.cta_view_results`/`results_count`/`reviews_count`, `booking.session_count`. Zero renders
|
||||
«پرستاری یافت نشد» / «بدون نظر» / «ویزیتی نیست», never «۰ پرستار».
|
||||
`search/SearchScreen.tsx` doesn't need a call-site change — `cta_view_results` is only reached
|
||||
when `count > 0` (the sibling `cta_zero_title` branch handles the zero state), so the ICU `=0` case is
|
||||
a defensive designed fallback, not the primary render path.
|
||||
- en's own `cta_view_results`/`results_count` `=0` case fixed from the audit's flagged "View no nurses" /
|
||||
"No nurses" to "No nurses found" (`cta_view_results` restructured so "View" only prefixes the non-zero
|
||||
cases — `{count, plural, =0 {No nurses found} one {View # nurse} other {View # nurses}}` — rather than
|
||||
gluing "View" onto a full sentence).
|
||||
|
||||
### 3.4 — Arrows out of strings
|
||||
- 4 keys per catalog had a literal ←/→ (not 5 — `payment.cta_pay` already lost its arrow before this
|
||||
phase): `booking.continue_payment`, `auth.nurse_switch`/`customer_switch`, `admin.cfg_history_change`.
|
||||
All four are now plain text; direction moves into a mirrored `AppIcon icon="forward"` (registered in
|
||||
`DIRECTIONAL_ICONS`, auto-flips under `[dir='rtl']` regardless of host component — verified by reading
|
||||
`AppIcon.tsx`, no per-call-site mirroring logic needed):
|
||||
- `bookings/request/[id]/page.tsx`'s continue-to-payment button: `endIcon="payment"` → `endIcon="forward"`
|
||||
(fixes the en wrong-direction arrow **by construction** — the icon mirrors, the string never carried
|
||||
direction to begin with).
|
||||
- `PhoneStep.tsx`'s role-switch link: an inline `<AppIcon icon="forward" size={14}>` after the text.
|
||||
- `admin/config/page.tsx`'s config-history diff: `cfg_history_change` split into
|
||||
`cfg_history_change_old`/`_new` (each holding one interpolation), rendered as two `Typography`s joined
|
||||
by a mirrored `AppIcon icon="forward"` — the old defect (a translator hand-mirroring `{old} ← {new}`)
|
||||
can't recur.
|
||||
- Grep for `←|→` across both catalogs → zero hits (STYLE.md prose itself has none either).
|
||||
|
||||
### 3.5 — Trust-moments copy pass
|
||||
- **BNPL de-jargon**: `bnpl.ownership_note` rewritten reader-first, «نکول» removed entirely — "you pay
|
||||
installments directly to the provider; Balinyaar receives the full amount up front, and if an
|
||||
installment goes unpaid, that risk sits with the provider — never you or the nurse" (both catalogs).
|
||||
Kept provider-agnostic (no `{provider}` interpolation) because the call site (`MethodStep.tsx`) shows
|
||||
this line **before** a provider is chosen, ahead of the provider list.
|
||||
- **OTP-screen reassurance** and **checkout "why is this safe"**: found **already delivered** —
|
||||
`AuthCard`/`TrustBullets` (ui-phase-3) already renders three trust bullets (verified nurses, escrow
|
||||
payment, support) directly under the phone-entry card on both the customer and nurse login steps, and
|
||||
`EscrowExplainer` (ui-phase-6) already supplements `EscrowNotice` with a 3-step visual + the
|
||||
cancellation-policy implication at checkout. Neither needed new copy; verified by reading `AuthCard.tsx`/
|
||||
`PhoneStep.tsx`/`EscrowExplainer.tsx` rather than assumed from the audit's older snapshot.
|
||||
- Verification pipeline naming + status vocabulary: see above.
|
||||
|
||||
### 3.6 — Config-served policy numbers
|
||||
- New `client/src/constants/policy.ts`: `DISPUTE_WINDOW_HOURS=72`, `CANCELLATION_LEAD_HOURS=24`,
|
||||
`REFUND_ETA_MIN_BUSINESS_DAYS=7`, `REFUND_ETA_MAX_BUSINESS_DAYS=10` — re-exported via
|
||||
`src/constants/index.ts`.
|
||||
- `payouts.explainer_point_2` takes `{hours}`; `refunds.lead_gt_24h`/`lead_lt_24h` take `{hours}`;
|
||||
`refunds.eta_business_days` takes `{minDays}`/`{maxDays}` (both catalogs). Wired at the 3 call sites:
|
||||
`nurse/earnings/page.tsx`'s `ExplainerCard`, `CancellationPolicyDisclosure.tsx`, `RefundEtaBanner.tsx`.
|
||||
`services/refunds/constants.ts`'s `BNPL_REFUND_ETA_BUSINESS_DAYS` now sources from
|
||||
`REFUND_ETA_MAX_BUSINESS_DAYS` instead of its own hardcoded `10`, so the mock's projected refund date and
|
||||
the displayed ETA window can never drift apart.
|
||||
- **REQ-065** filed (`dev/shared-working-context/frontend/requests/for-backend.md`) — a
|
||||
public/authenticated policy-config read; verified first that only admin-scoped `platform_config/*`
|
||||
exists (per the contract docs), so this is a genuine gap, not a guess.
|
||||
|
||||
### 3.7 — Honest search empty state
|
||||
- **Already fixed by ui-phase-4** — `search.empty_suggest_city` (the Mashhad/Isfahan/Shiraz nonsense
|
||||
suggestion) was already deleted and replaced with `empty_suggest_date` in both catalogs. Verified by
|
||||
grep (`مشهد|اصفهان|شیراز` → no matches) rather than re-implemented.
|
||||
|
||||
### 3.8 — Motion pass
|
||||
- `globals.css`: one `bal-fade-in` keyframe (150–200ms fade + 4px slide, `var(--bal-motion-base)` +
|
||||
`var(--bal-easing-standard)`) applied via a `data-bal-route-fade` attribute selector, and **the single
|
||||
reduced-motion gate for the whole app** — a universal `*, *::before, *::after { animation-duration:
|
||||
0.01ms !important; transition-duration: 0.01ms !important; … }` under
|
||||
`@media (prefers-reduced-motion: reduce)`. This is the one rule that makes MUI's own JS-driven
|
||||
Dialog/Drawer/Menu/Collapse/Fade transitions collapse too (they don't read CSS custom properties, so a
|
||||
token-only gate wouldn't reach them) — no component anywhere needs its own reduced-motion branch.
|
||||
- `tokens.css` additionally zeroes `--bal-motion-fast/base/slow` under the same media feature, for any
|
||||
future consumer that reads the token value directly.
|
||||
- New shared component `components/common/RouteFadeIn/` (tested) — wraps `{children}`, keyed on the
|
||||
locale-stripped pathname (`@/i18n/navigation`'s `usePathname`) so it remounts (replaying the fade) on
|
||||
navigation but never on an in-place re-render. Mounted inside the `ErrorBoundary` in **all five** shells
|
||||
(`CustomerLayout`, `TopBarAndSideBarLayout` — nurse/admin/partner, `FocusedLayout`, `PublicLayout`) —
|
||||
every route in the app gets the fade, not a hand-picked subset.
|
||||
- `theme.ts`: `MuiDialog`/`MuiDrawer`/`MuiPopover`/`MuiMenu` all get an explicit
|
||||
`defaultProps.transitionDuration: { enter: 200, exit: 120 }` in **one place** (matching
|
||||
`--bal-motion-base`/`-fast`) instead of MUI's per-variant defaults, so every dialog/bottom-sheet/menu in
|
||||
the app now shares one calm timing.
|
||||
- Skeleton→content crossfade: the `data-bal-route-fade` primitive is reusable for this (any screen can
|
||||
attach it to its populated-state branch); demonstrated on `search/results/page.tsx`'s results grid
|
||||
(§3.10) as a worked example rather than retrofitted to every list screen in the app — flagged as a
|
||||
mechanical follow-up below, not a silent scope cut.
|
||||
|
||||
### 3.9 — A11y sweep
|
||||
- **`AppIconButton`**: the underlying `IconButton` now always receives `aria-label={title}` directly (not
|
||||
only via the `Tooltip` wrap), so a **disabled** icon-only button — where the Tooltip isn't rendered at
|
||||
all — keeps an accessible name. A caller-supplied `aria-label` still wins (spread order). New test:
|
||||
"keeps an accessible name from `.title` even when disabled". `useMemo` dependency array fixed
|
||||
(`react-hooks/exhaustive-deps`) as part of the change.
|
||||
- **`aria-live`**: `CountdownTimer`'s one-time elapsed transition now carries `aria-live="polite"` (the
|
||||
coarse-mode label already had it from an earlier phase; the fine per-second clock deliberately still
|
||||
doesn't — spam risk, unchanged). `ErrorState` (the one "query failed" pattern, ~55 sites) gets
|
||||
`role="alert"` — one change covers every error/retry region in the app. `ErrorBoundary` gets the same.
|
||||
`PaymentStateCard` (the one terminal/wait-state card for card + BNPL checkout, including the
|
||||
pending→succeeded/failed poll) gets `aria-live="polite"`.
|
||||
- **`aria-expanded`/`aria-controls`**: `EscrowExplainer`'s toggle gained `aria-controls` (it already had
|
||||
`aria-expanded`); the admin ticket thread's refund-panel toggle (`admin/tickets/[id]/page.tsx`) gained
|
||||
both (it had neither). `nurse/earnings`'s `ExplainerCard` and `AuditLogRow`/`EmergencyPlaybookRow`
|
||||
already had both from earlier phases — verified, not re-touched.
|
||||
- **Contrast**: `NurseDashboardScreen.tsx`'s unread-notifications label was the one real terracotta-on-text
|
||||
finding (`color: 'var(--bal-secondary)'` on a `body2` unread count) — swapped to
|
||||
`--bal-secondary-dark`. Every other `--bal-secondary` text usage found by grep was already
|
||||
`--bal-secondary-dark` (BNPL screens) or an icon/background (non-text contrast rules, not the AA text
|
||||
threshold the audit flagged).
|
||||
|
||||
### 3.10 — Desktop-aware layouts (scope-boxed to the two named surfaces)
|
||||
- **Checkout** (`bookings/checkout/page.tsx`): above `md` (~900px), a two-column layout — the
|
||||
summary/total/countdown/breakdown/escrow content on the reading side (flex `62%`), a `position: sticky`
|
||||
order-summary panel with the pay CTA on the other. The pay-action JSX (total, CTA, secure-gateway note,
|
||||
BNPL button, inline error) is a single `payActions` value rendered in **both** the desktop panel and the
|
||||
mobile `StickyActionBar` — same handlers, same state, no duplicated logic; only one is visible at a time
|
||||
via `sx={{ display: { xs: …, md: … } }}`, the same pattern the codebase already uses for
|
||||
`BottomBar`/`CustomerDesktopNav`.
|
||||
- **Search results** (`search/results/page.tsx`): the populated/skeleton lists switch from a single-column
|
||||
`Stack` to a `display: grid` with `gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }` — two columns above
|
||||
`md`. Bounded by the customer shell's existing `CONTENT_MAX_WIDTH` (800px, unchanged, shared across the
|
||||
whole customer app) — each card gets meaningfully more width than the old full-width single column, but
|
||||
a true full-bleed desktop layout or list+detail split would need a per-route shell override, which is
|
||||
exactly the "full responsive pass" the phase doc explicitly defers post-chain. Flagged, not silently cut.
|
||||
|
||||
### 3.11 — Final QA walkthrough
|
||||
|
||||
**Static verification performed** (all green): `npm run check` (type + lint + the new `lint:copy`),
|
||||
`npm run test:ci` (115 suites / 526 tests, including 2 new/extended ones — `RouteFadeIn.test.tsx`,
|
||||
`AppIconButton.test.tsx`'s new disabled-name case), a key-parity script (`fa.json`/`en.json` — 1891 leaf
|
||||
keys each, zero one-sided keys, matching array lengths on `legal.terms_sections`/`privacy_sections`), and
|
||||
a manual grep pass for stray hex literals in every changed file (none found — every new color reference is
|
||||
a `var(--bal-*)` token).
|
||||
|
||||
**Could not perform a live in-browser visual pass.** This sandbox has no headless-browser tool
|
||||
(`chromium-cli` is not installed) and direct HTTP access to the dev server's `localhost` port returns a
|
||||
proxy `502` rather than reaching Next.js (confirmed: the dev server itself started cleanly — `✓ Ready in
|
||||
5.8s` in its log — the request never reached it). Per this repo's own verification guidance, I'm stating
|
||||
this explicitly rather than claiming a visual check that didn't happen. **A human should run `npm run dev`
|
||||
and spot-check, on `/fa` and `/en` × light and dark × a narrow and a ≥1100px viewport:**
|
||||
- [ ] Login (`/login`): brand reads «بالینیار» identically everywhere it appears (header, trust bullets,
|
||||
account-error retry copy); trust bullets render under the phone-entry card.
|
||||
- [ ] Search (`/search` → results): filters producing zero results show «پرستاری یافت نشد» (fa) / "No
|
||||
nurses found" (en) — never «۰ پرستار»/"View no nurses"; at ≥900px, result cards render two-up.
|
||||
- [ ] Nurse EVV (`/nurse/visits`): the visits subtitle spells out EVV once; attempting a check-out with no
|
||||
open check-in shows the corrected sentence (no «ورود بازی»).
|
||||
- [ ] Bank (`/nurse/bank`): the verified chip reads «تأییدشده» (not «تاییدشد»).
|
||||
- [ ] Checkout (`/bookings/checkout?request_id=…`): at ≥900px, a two-column layout with a sticky right-side
|
||||
pay panel; at <900px, the original bottom sticky bar; the "continue to payment" button's chevron
|
||||
points the reading-forward direction on both locales (mirrored, not baked into the string).
|
||||
- Currently only reachable via the request-flow with a real/mocked `accepted_awaiting_payment` request.
|
||||
- [ ] BNPL comparison (`/bookings/checkout/bnpl`): the ownership note reads plainly, no «نکول».
|
||||
- [ ] Any route transition: a calm ~150–200ms fade/slide plays once; with DevTools' "Emulate CSS media
|
||||
feature `prefers-reduced-motion: reduce`" enabled, everything appears instantly (no fade, no MUI
|
||||
dialog/menu transition either).
|
||||
- [ ] Any icon-only disabled button (e.g. a paused/loading admin action): inspect the accessibility tree —
|
||||
it still has a name.
|
||||
- [ ] Config history (`/admin/config`, open a row's history drawer): the old→new diff shows a mirrored
|
||||
arrow icon between two values, not a baked-in `←`/`→` character.
|
||||
|
||||
## What is now testable (and exactly how)
|
||||
|
||||
1. `cd client && npm run check` — passes, including `lint:copy`. Temporarily add «بالین یار» (plain
|
||||
space) to any `fa.json` value → `npm run lint:copy` fails with the exact key path; revert.
|
||||
2. `npm run test:ci` — 115 suites / 526 tests pass, including the two touched/added shared-component tests.
|
||||
3. Grep both catalogs for `←|→` → zero hits. Grep for `تایید` (hamza-less) → zero hits (only `تأیید` and
|
||||
its compounds remain). Grep for `جستوجو`/`جست و جو` → zero hits.
|
||||
4. The four visual-axis items above, once a human can reach a browser.
|
||||
|
||||
## What is mocked / waiting on a real service
|
||||
|
||||
- **REQ-065** (new, this phase) — public/authenticated policy-config read (dispute-window hours,
|
||||
cancellation lead hours, refund ETA days). Until delivered, `client/src/constants/policy.ts` is the
|
||||
single source; every message key that used to hardcode a policy number now takes it as an ICU param from
|
||||
this file. No mock-registry entry needed — this isn't a seam behind a DI interface, it's a constants file
|
||||
standing in for a config read that doesn't exist yet for a non-admin caller.
|
||||
- No other new mocks this phase; no `services/{domain}` seam was touched.
|
||||
|
||||
## Contracts
|
||||
|
||||
- Consumed: none new. REQ-065 filed against the existing `platform_config` admin-only surface
|
||||
(`dev/contracts/domains/` — verified no public projection exists before filing, per the phase's own
|
||||
instruction not to file speculatively).
|
||||
|
||||
## Docs updated
|
||||
|
||||
- `client/messages/STYLE.md` — new, the Persian style guide (this phase's own deliverable).
|
||||
- `client/CLAUDE.md` — see the diff in this change: mentions `messages/STYLE.md` + `npm run lint:copy` in
|
||||
the i18n section, the reduced-motion gate location (`globals.css`), `RouteFadeIn` in the component-library
|
||||
table, and `constants/policy.ts` in the constants section.
|
||||
- `dev/shared-working-context/frontend/requests/for-backend.md` — REQ-065 appended.
|
||||
|
||||
## Follow-ups for later phases (post-chain)
|
||||
|
||||
- **Skeleton→content crossfade beyond the one worked example.** `data-bal-route-fade` is reusable
|
||||
(a one-line addition per screen) but wasn't retrofitted onto every list/detail page in the app — a
|
||||
mechanical, low-risk follow-up, not a defect.
|
||||
- **A true desktop search layout** (list+detail split, or breaking out of the 800px `CONTENT_MAX_WIDTH`
|
||||
for this one route) — the phase doc explicitly defers "a full responsive pass" post-chain; the 2-column
|
||||
grid shipped here is the scope-boxed interim step.
|
||||
- **REQ-065** — once delivered, replace `constants/policy.ts`'s hardcoded numbers with a fetched config
|
||||
read (the ICU-param call sites don't change, only where the numbers come from).
|
||||
- A live in-browser visual confirmation of the walkthrough checklist above — this environment couldn't run
|
||||
one; flag to the human reviewer.
|
||||
|
||||
## Memory
|
||||
|
||||
A `project`-type memory note was saved (`ui_phase_12_copy_motion_polish.md`) summarizing the STYLE.md
|
||||
decisions, the reduced-motion gate's location and mechanism, and the sandbox's browser-verification gap, so
|
||||
a future agent doesn't have to rediscover any of it.
|
||||
Reference in New Issue
Block a user