Files
baya-monorepo/dev/shared-working-context/frontend/STATUS.md
T
2026-07-10 18:46:16 +03:30

426 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Frontend status log (append-only)
One block per completed frontend phase. Newest at the top. Frontend lane writes here; backend reads
for awareness.
<!-- TEMPLATE — copy for each phase
## frontend-phase-N-bM — <Title> — <YYYY-MM-DD>
- **Shipped:** <screens/flows/services/hooks/components in one or two lines>
- **Consumes:** dev/contracts/domains/<domain>.md (backend phase bM)
- **Mocked client-side:** <which services are mocked pending which backend phase>
- **Gate:** npm run check green / tests green
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
-->
## frontend-phase-14-b15 — Messaging (tickets) & notifications — 2026-07-10
- **Shipped:** the social/communication layer for the customer **and** nurse apps (role decides chrome, not
the components; admin lens DEFERRED to f15). Two new domains — **`services/tickets`** (`useMyTickets` /
`useTicket` = the whole thread in one `detail(id)`, no message pagination / `useTicketThread` = select over
detail / `useOpenTicket` / `usePostMessage` **optimistic, draft-preserving**, reconcile by `clientMessageId`)
and **`services/notifications`** (`useNotifications` unread-first / `useUnreadCount` the **polled** bell —
count-only, stale-while-revalidate / `useMarkNotificationRead` + `useMarkAllRead` optimistic `setQueryData`).
Screens shared by both shells: **My Tickets inbox** (`/support/tickets`, `/nurse/support/tickets`) with
prominent `referenceCode` + unread indicator + null-safe linked-entity hint + Contact-support dialog (shows the
new `referenceCode`); **thread** (`/…/tickets/[id]`) role-aware bubbles + sticky composer; **notification
center** (`/notifications`, `/nurse/notifications`) unread-first, mark-read-on-open + mark-all, deep-links via
`notificationDeepLink`. **Notification bell** in the customer TopBar + the nurse shell (subscribes to the poll so
only it re-renders). **Emergency banner** + **"Get support / Open ticket"** on the f8 booking detail
(`BookingSupportEntry`, reuses the cached booking + nurse-gated care query — no refetch); a support icon in the
customer TopBar + a Support item in the nurse sidebar. New shared/tested composites: `MessageBubble`,
`TicketListCard`, `EmergencyBanner`, `NotificationRow`, `NotificationBellView`, `ContactSupportDialog` (+
screens); `notificationDeepLink`/`parseNotificationData` unit-tested; `support`/`send` icons; `tickets` +
`notifications` i18n namespaces + `nav.support` (both locales).
- **Consumes:** dev/contracts/domains/messaging-notifications-admin.md (b15 tickets) + config-reference.md (b1
notifications) + openapi/swagger.v1.json. Real & mapped 1:1 by the client APIs: `POST/GET /tickets`,
`GET /tickets/{id}`, `POST /tickets/{id}/messages`; `GET notifications/get_notifications`,
`get_unread_count`, `POST mark_notification_read` / `mark_all_read`.
- **Critical rules honoured:** `is_internal` is **never** modelled in the user-app types — both API mappers DROP
any internal message (server-strip mimic), no internal affordance anywhere; the emergency surface is a
**post-confirmation `tel:` playbook only** (nurse-gated care contact, no VoIP seam, never a general phone);
the unread **count** polls politely (60s interval + 45s staleTime + refetch-on-focus, auth-gated) and the list
is never polled; `data_json` is parsed into a typed union and degrades to no-deep-link.
- **Mocked client-side:** `services/tickets` (`USE_TICKETS_MOCK`) — b15 is live and `ticketsClientApi` maps it
1:1, but the linked bookings are mock-primary and the summary lacks `unreadCount`/`lastMessageAt` (REQ-028);
`services/notifications` (`USE_NOTIFICATIONS_MOCK`) — b1 is live and mapped 1:1, but nothing dispatches
notifications client-side yet. Both default `true`; swap is one flag. See mocks-registry.
- **Gate:** npm run check green · npm run test:ci green (66 suites / 289 tests, +32) · production build compiles
+ type-checks clean (a **pre-existing** "Missing .env variable" prerender guard fails on `/en/addresses` +
`/en/nurse/verification/identity` only — unrelated to this phase; sibling pages under the same modified shells
prerender fine). 6-dimension adversarial review with per-finding verification.
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-028: `unreadCount`+`lastMessageAt` on the
ticket summary, a message author label / masked-name confirmation, a by-booking user ticket lookup, and an
optimistic `clientMessageId` idempotency field).
## frontend-phase-13-b14 — Reviews & patient care records — 2026-07-10
- **Shipped:** the last feature-domain phase. Two new domains — **`services/reviews`** (`useNurseReviews`
infinite published-only aggregate+list / `useReviewEligibility` / `useMyReviewForBooking` / `useCreateReview`
— invalidates eligibility+my-review, NEVER the public list) and **`services/patientRecords`**
(`usePatientCareRecord` / `useRecordAccess` / `usePatientHistory` / `useUpdateCareRecord` CUSTOMER-only /
`useCreateVisitNote` NURSE-only). Screens: `/bookings/[id]/review` (RatingInput + ReviewTagSelector, gated
completed+can_review+1:1, under-review state) + `LeaveReviewCta` on the customer booking detail; C3 nurse
profile **reviews tab** (published-only aggregate+count+infinite list); E2 **`/patients/[id]/record`** (4 tabs
داروها/روتین/سوابق/وظایف + ownership banner + customer edit + non-leaking access-denied); nurse
**`NurseVisitNotesPanel`** (append-only task-checklist+note composer BELOW the f8 EVV banner). New shared
composites RatingInput/ReviewTagSelector/VisitNoteCard/PatientHeader (each tested; PatientCard now composes
PatientHeader + gained `onOpen`); `usePatient(id)`; new icons; `reviews`+`records` i18n namespaces (both
locales). Extended the f8 bookings mock: completed booking **5005** + cross-mock read mockGetBookingForReview.
- **Consumes:** dev/contracts/domains/reviews-records.md + openapi/swagger.v1.json (backend-phase-14). REAL &
mapped 1:1: `POST bookings/{id}/review`, `GET nurses/{id}/reviews`, `GET`/`POST patients/{id}/care_records`.
- **Mocked client-side:** `services/reviews` (`USE_REVIEWS_MOCK`) — eligibility + my-review are REQ-026 gaps,
moderation is admin-only/f15 (dev `__mockPublishSubmittedReview`). `services/patientRecords`
(`USE_PATIENT_RECORDS_MOCK`) — the visit-note history/append are REAL b14; the family-owned record
(meds/routine/tasks) + access check have NO backend (REQ-027). Both default `true`; swap is one flag. See
mocks-registry.
- **Gate:** npm run check green · npm run test:ci green (all suites, +17) · npm run build green with
NEXT_PUBLIC_API_URL set. 6-dimension adversarial review with per-finding verification.
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-026 review eligibility + my-review + masked
author; REQ-027 family-owned care record + access + structured taskResults — flags the E2 record has no
data-model entity).
## frontend-phase-12-b13 — Nurse earnings & payout history — 2026-07-10
- **Shipped:** the **last money-path frontend phase** — the read-only **nurse earnings** surface. A **new
`services/payouts` domain** (types/keys/constants/apis[client+mock]/4 read-only hooks + hooks-only barrel)
and three nurse screens under the nurse shell: **`/nurse/earnings`** (net payable balance + four-bucket
breakdown + a plain-Persian cadence/dispute-window explainer + a state-segmented earnings list), **`/nurse/
earnings/payouts`** (payout history list), and **`/nurse/earnings/payouts/[id]`** (payout/batch reconciliation
detail — money decomposition + masked IBAN + transfer ref + the exact bookings covered). Three shared tested
composites: **`EarningsBalanceHeader`** (net balance with an explicit **negative "owed back"** state — never a
bare minus), **`EarningsRow`** (three-amount `gross commission = your payout` breakdown via `PriceBreakdown`
+ one of four visually-distinct state chips + state affordance: pending shows a **display-only** dispute-window
countdown reusing `CountdownTimer`), **`PayoutHistoryRow`** (net transferred + status chip + masked IBAN +
failure banner). Deep-links to the f8 booking detail (`/nurse/visits/{id}`); the `earnings` nav item +
`PaidOutlined` icon + a `payouts` i18n namespace (82 keys, both locales in sync) added. **Strictly read-only**
— no transfer/retry/batch/mutation; a failed payout shows its reason with **no retry** (admin action). Money is
IRR digit-strings via the f0 money util (`gross = commission + payout`; the BNPL provider commission never
appears); eligibility/dates/amounts are server truth (never computed client-side).
- **Consumes:** dev/contracts/domains/payouts.md (backend phase b13) — only `GET api/v1/nurse_payouts/history`
maps a live nurse route; the summary/earnings-list/nurse-payout-detail are contract gaps (REQ-025).
- **Mocked client-side:** `services/payouts` via `payoutsMockApi` (`USE_PAYOUTS_MOCK=true`) — self-contained,
money-correct fixtures covering all four earnings states + all four payout statuses + a `failed` payout + a
**negative net balance** (`MOCK_SCENARIO='clawback_heavy'`). Swap is one flip once REQ-025 lands.
- **Gate:** npm run check green · npm run test:ci green (242 tests, +3 suites) · npm run build green with
NEXT_PUBLIC_API_URL set.
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-025).
## frontend-phase-11-b12 — BNPL installment checkout (D1D5) — 2026-07-10
- **Shipped:** the **alternate checkout branch** off C6 — a **new `services/bnpl` domain** (types/keys/
constants/invalidations/apis[client+mock]/7 hooks + barrel) and the five wireframe screens under the
customer shell, terracotta-financial: **D1 روش پرداخت** (provider chooser off `useBnplOptions`, full-card
fall-back), **D2 انتخاب طرح** (single-select plan), **D3 اعتبارسنجی** (کد ملی + prefilled موبایل +
consent-gated → approved-with-ceiling / declined+ceiling-exceeded → **card fall-back**), **D4 تایید طرح و
قرارداد** (served repayment table + ownership note + contract-gated final action → provider handoff), and
**D5 پیگیری اقساط** in the **کیف‌پول** tab (provider-reported outstanding balance + due list + early-pay
**provider hand-off**). The handoff (gateway harness → return) settles the down-payment and **routes to the
reused f9 confirmation** (`?method=bnpl`), so the booking confirms exactly as the card path. Two shared
tested composites: `BnplPlanCard`, `InstallmentScheduleRow` (reused D4+D5). C6's «پرداخت اقساطی» is enabled;
the `installments` icon + the `bnpl` i18n namespace (87 keys, both locales in sync) added.
- **Consumes:** dev/contracts/domains/bnpl.md (backend phase b12) — `eligibility`/`initiate`/order-by-id map
1:1; options/schedule/wallet-status/by-request-order/D3-KYC are contract gaps.
- **Mocked client-side:** `services/bnpl` via `bnplMockApi` (`USE_BNPL_MOCK=true`) — the settle bridge reuses
the f9 conversion (a settled BNPL order = a card payment net-of-fee) + seeds a provider-reported Wallet
plan. See mocks-registry. Money = served IRR digit-strings end-to-end (mock computes with BigInt; components
only format). **D5 is provider-reported status, NOT a Balinyaar ledger; early-pay hands off to the provider.**
- **Reviewed:** 6-dimension adversarial multi-agent review + verify pass — 5 confirmed findings, all fixed
(return-page window-expired copy/CTA, real getBnplOrder order-id keying, a needless poll fetch, dead `col_*`
keys + surfacing `ownership_note` at D1, a dark-mode glyph-contrast token).
- **Gate:** npm run check green · npm run test:ci green (223 tests, +9) · npm run build green with
NEXT_PUBLIC_API_URL set.
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-022 options+schedule, REQ-023 eligibility
KYC inputs, REQ-024 wallet status + customer bookingId + by-request order lookup).
## frontend-phase-10-b11 — Cancellation & refund status (customer) — 2026-07-10
- **Shipped:** the trust-first **exit** — a **new `services/refunds` domain** (types/constants/keys/
invalidations/apis[client+mock]/3 hooks + barrel) and two customer screens: the **cancellation flow**
`/bookings/[id]/cancel` (discloses the resolved policy tier + **refund % + fee %** + the Toman
refund-vs-fee split + the multi-session refundable/locked breakdown **before** confirm, gated behind an
explicit acknowledgement → `useCancelBooking` → refund status) and the read-only **refund status**
`/bookings/[id]/refund_status` (3-step **submitted → on-its-way → completed** stepper, the honest BNPL
**~710-business-day** ETA, `failed`=contact-support with **no retry**, empty state). The customer
booking detail now hangs a **Cancel** CTA / **refund section** (`CustomerBookingActions`, reuses the
cached booking query). Three shared tested composites: `CancellationPolicyDisclosure`, `RefundStatusCard`,
`RefundEtaBanner`. New `refunds` i18n namespace (71 keys, both locales).
- **Load-bearing rules honored:** refunds are **admin-approved — the customer never self-issues** (no
issue/approve/retry control anywhere; failed = contact-support); the fee/refund % is **disclosed +
acknowledged before confirm**; money = IRR digit-strings, **BigInt only** (integer parts-per-10000 —
refund+fee and the fee-leg split reconcile to the rial, `PriceBreakdown` dev-guarded); **BNPL surfaced
honestly** (the ~710-day window + `expected_customer_refund_eta`, money flows *through the provider*,
never instant, never Balinyaar→customer) — and **suppressed on a failed refund** (no success-framed
"money is on its way"); **per-session** — only un-started sessions refundable, completed-and-verified stay
locked; labels are i18n keys off the codes, **never raw enum codes**; polling **only while non-terminal**;
cancel invalidates booking detail/lists + **primes** the refund cache.
- **Consumes:** dev/contracts/domains/refunds-invoices.md (b11 — `refunds/{id}/status` shape + enums;
`refund_status`=requested|approved|processing|succeeded|failed|rejected, `refund_channel`=psp_card|
bnpl_revert|manual, `expected_customer_refund_eta` is a date). **Not served by the contract (admin-only):**
the customer cancel command, the pre-cancel policy preview + per-session flags, refund-by-booking, and the
fee-leg decomposition on the customer status → REQ-019/020/021.
- **Mocked client-side:** `services/refunds` via `refundsMockApi` (**USE_REFUNDS_MOCK=true, primary**) — it
reads the shared f8 bookings store to resolve the tier by lead time + per-session refundability, flips the
booking to `cancelled` (`mockMarkBookingCancelled` stamps the b9 snapshot), and drives card-immediate
(`succeeded`) / BNPL-`processing``succeeded` refunds with a real ETA + a seeded `failed` refund (5004) and
the outside-policy `409`. Added bookings-store seeds 5003 (mid-engagement mixed) + 5004 (cancelled) + two
non-seam exports. Real `refundsClientApi` maps `refunds/{id}/status` 1:1 and targets the proposed slugs for
the gaps; one flag flip when REQ-019/020/021 land.
- **Reviewed:** 4-dimension adversarial review + per-finding verify — 3 confirmed findings, all fixed
(failed-state ETA/amount suppression = BNPL-honesty; two missing `no_refund_*` i18n keys; a UTC/local
`daysUntil` mismatch that flipped the tier in +offset timezones).
- **Gate:** npm run check green · npm run test:ci green (**214 tests, +10**).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-019 cancel command, REQ-020 policy
preview + per-session flags + canonical policy codes, REQ-021 refund-by-booking + customer decomposition).
## frontend-phase-9-b10 — Checkout, card payment & invoice — 2026-07-10
- **Shipped:** the money moment — a **new `services/payment` domain** (types/keys/constants/
apis[client+mock]/invalidations/5 hooks + barrel) and the customer checkout flow: **C6 خلاصه و پرداخت**
`/bookings/checkout?request_id=` (acceptance badge, served **reconciling** service-cost/کارمزد/مالیات/مبلغ کل
breakdown, verbatim **escrow notice**, payment-window countdown, «ادامه پرداخت ←» with an
**idempotency-key-per-attempt**, disabled BNPL seam for f11), the **card states** (initiating → redirect →
dev **mock-gateway harness** `/bookings/checkout/gateway` → return `/bookings/checkout/return` with a
**backoff pending-callback poll** → succeeded/failed/expired), the **confirmation**
`/bookings/checkout/confirmation` («مشاهده رزرو» + «دانلود فاکتور»), and the **invoice**
`/bookings/[id]/invoice` (VAT-on-commission line, read-only مودیان state, pdfUrl download or print
receipt). Three shared tested composites: `PriceBreakdown`, `EscrowNotice`, `PaymentStatusBadge`. New
`payment` i18n namespace (53 keys, both locales).
- **Load-bearing rules honored:** money = IRR digit-strings, **BigInt only** (integer parts-per-10000 rate
math in the mock — zero floats); the breakdown **must reconcile** (`PriceBreakdown` dev-guards it; rows
are served, VAT never derived client-side); **VAT on the commission only** (invoice line labelled
accordingly); escrow copy **verbatim** in fa (pinned by test against fa.json), info tone never error;
**409 = benign convergence** (re-reads the outcome, never an error toast); poll uses **geometric backoff,
stops on terminal + bounded attempts**; success flips the booking **by cache invalidation** (exact keys,
no refetch storm).
- **Consumes:** dev/contracts/domains/payments.md (b10 — initiate route + `Idempotency-Key` header +
`InitiatePaymentResult`; status enum `pending|succeeded|failed`) and the invoice slice of
refunds-invoices.md (b11 — `GET invoices/{bookingId}`, `InvoiceDto`), casing verified against
swagger.v1.json. **Not served by any contract:** a checkout summary, a client transaction read, and the
converted request's booking id → REQ-016/017/018.
- **Mocked client-side:** `services/payment` via `paymentMockApi` (**USE_PAYMENT_MOCK=true, primary**) —
it is the missing **conversion trigger bridging the f7 ↔ f8 mock stores**: capture converts the request
(`converted` + client-augmented `bookingId`), inserts a **confirmed** booking into the f8 store, and
auto-issues the b11-shaped invoice, so C5 → C6 → gateway → confirmation → booking detail → invoice runs
end-to-end in one session. The dev **mock-gateway page is a test harness, not a product feature**. Real
`paymentClientApi`: initiate/invoice = published contract; summary targets the REQ-016 proposed slug;
outcome maps `booking_requests/get` (REQ-017).
- **Gate:** npm run check green · npm run test:ci green (204 tests, +9) · production build green.
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-016 checkout summary, REQ-017 payment
outcome + bookingId, REQ-018 customer invoice availability post-capture).
## frontend-phase-8-b9 — Booking detail, sessions & nurse EVV — 2026-07-10
- **Shipped:** the post-payment engagement — a **new** `services/bookings` domain (the sibling of
`bookingRequests`, NOT a rename): types/keys/constants/apis[client(1:1 b9)+mock+serverApi]/8 hooks +
barrel, plus the `evv/locationProvider.ts` **ILocationProvider** GPS seam. Screens: customer **رزروها
list** `/bookings` + **booking detail** `/bookings/[id]` (BookingDetailView, customer view), nurse
**ویزیت امروز** `/nurse/visits` (today-sessions EVV feed) + **nurse booking detail** `/nurse/visits/[id]`
(EVV controls + gated care). Seven shared tested composites under `src/components/booking/`:
`BookingDetailView`, `BookingStatusTimeline` (server-truth 7-status), `SessionList``SessionCard`
(per-session schedule/status/EVV CTA), `EvvStatusBanner` (advisory in/out-of-range/no-gps), `CareInstructionsCard`,
`BookingMoneySummary`, + `useEvvController`. i18n `booking` extended (`bstatus_*`/`sstatus_*`/`evv_*`/`care_*`/
`money_*`/`list_*`) both locales; new icons (check_in/out, gps, clinical, medication, emergency, lock) +
`--bal-secondary-soft` token.
- **Load-bearing rules honored:** **two-stage disclosure is a UI gate**`useCareInstructions` is
`enabled` only for the assigned-nurse view on a `confirmed`+ booking; the customer NEVER fires it (proven
by test). **EVV mismatch/GPS-denial is advisory, never a block** — out-of-range check-in still succeeds
(warning-tokened banner, not error); denial still submits. **Timeline = server truth** (never advanced
client-side); **money display-only** (gross/commission/payout rendered as sent, never summed/re-split;
`payoutEligibleAt` never recomputed); single-visit renders one session row through the same card; EVV
mutations **invalidate** detail+sessionEvv+today+list.
- **Consumes:** dev/contracts/domains/bookings-evv.md (b9) + swagger `BookingDetailDto`/`BookingSessionSummaryDto`/
`VisitVerificationDto`/`CareInstructionsDto``services/bookings/types.ts` derives from these 1:1.
- **Mocked client-side:** `services/bookings` via `bookingsMockApi` (**USE_BOOKINGS_MOCK=true, primary**) —
seeds 2 confirmed bookings (one 3-session, one single-visit) + care + a check-in/out EVV state machine,
because a real booking only exists after `bookings/convert` runs on a paid request and both upstreams
(bookingRequests mock, card capture b10) aren't real client-side yet. Real `bookingsClientApi` maps the
routes 1:1; swap is one flag. Also the **ILocationProvider** GPS seam (`NEXT_PUBLIC_EVV_MOCK_GPS`
in_range|out_of_range|denied|off) — the first frontend seam recorded in mocks-registry.
- **Gate:** npm run check green · npm run test:ci green (195 tests, +22). Added a committed
`NEXT_PUBLIC_API_URL` default in `jest.setup.ts` (first test to render a service-hook component pulled
`@/config` at import).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-015: confirm the booking/session/EVV enum
string codes + the `checkInAddressMatch` tri-state semantics the client unions assume).
## frontend-phase-7-b8 — Booking request flow (customer request + nurse inbox) — 2026-07-09
- **Shipped:** the money-free request phase — `services/bookingRequests` (types/keys/constants/apis[client+
mock]/hooks + barrel) and screens **C4** `/bookings/request` (patient/variant/address/date+time + a
first-class 3-way caregiver-gender toggle + stage-1 notes; client-side validation gates the CTA; domain
400s surface as field/form errors; reuses f2 patients, f3 addresses + map preview, f4/search variants),
**C5** `/bookings/request/[id]` (summary card + 3-step tracker + polled status; response countdown →
accept flips to a 30-min payment countdown + checkout CTA / reject/expire/cancel/converted terminal
cards; cancel-with-confirm), the **nurse inbox** `/nurse/requests` (+ nurse nav item) and **detail**
`/nurse/requests/[id]` (only `customerNotes` + masked city/district; accept / reject-with-reason
invalidate inbox+detail), and a `/bookings/checkout` f9 stub. Two shared tested composites:
`CountdownTimer` (owns its 1s tick — only it re-renders; stops at zero), `BookingRequestSummaryCard`.
i18n `booking` (fully fleshed) + `nav.requests` in both locales.
- **Load-bearing rules honored:** deadlines are **server-frozen UTC** (client renders, never recomputes);
`required_caregiver_gender` is explicit + client-blocks a same-gender mismatch; two-stage disclosure (the
nurse UI never renders address/clinical fields — `get(id,'nurse')` masks); polling **stops** on a
terminal/`converted` status.
- **Consumes:** dev/contracts/domains/booking-requests.md (b8) — routes `booking_requests/{create,accept/{id},
reject/{id},cancel/{id},list,get/{id}}`, wire camelCase, action-style. `services/bookingRequests/types.ts`
derives from it.
- **Mocked client-side:** `services/bookingRequests` via `bookingRequestsMockApi` (**USE_BOOKING_REQUESTS_MOCK
=true, primary**) — a shared in-memory state machine so create → nurse inbox → accept/reject → C5-poll →
lazy expiry all demo end-to-end (b8 is live, but its inputs — search/patients/addresses — are themselves
mock-primary; and the DTO omits `variantPrice`, REQ-013). Real `bookingRequestsClientApi` maps the b8
contract 1:1; swap is one flag. Recorded in the phase report (not mocks-registry — backend DI seams only).
- **Gate:** npm run check green · npm run test:ci green (173 tests, +8) · npm run build green with
NEXT_PUBLIC_API_URL set (the only prerender failure without env is the pre-existing "Missing .env
variable!" — hits the existing `/fa/search` too, unrelated to f7).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-013 variantPrice on the DTO + nurse avatar;
REQ-014 inbox list-item variantLabel + patient age).
## frontend-phase-6-b7 — Search & discovery (find a verified, same-gender nurse) — 2026-07-09
- **Shipped:** the family discovery slice — `services/search` (types/keys/constants/apis/hooks + a shared
`filterParams.ts` C1↔C2 URL serializer) and screens **C1** `/search` (reused category grid + f3 region
picker + prominent same-gender facet + Toman price + live-count CTA; `useSearchFilters` colocated
controller with debounced price), **C2** `/search/results` (rating-sorted `NurseResultCard` list, all
four states incl. "relax filters" empty, load-more), **C3** `/search/nurse/[nurseId]` (TrustBadge + نظام
پرستاری badges, attribute chips, `ServicePriceRow` services, latest review, "درخواست رزرو" handoff).
Two shared tested components: `NurseResultCard`, `ServicePriceRow`. `/bookings/request` = f7 handoff stub.
i18n `search` (filled) + `booking` (seeded) in both locales. Reused f4 category grid, f5 TrustBadge, f3
geo picker, f0 money util — none rebuilt.
- **Headline caching:** the **filter object IS the query key** — reverting to a prior filter set is a cache
hit with zero network (keepPreviousData avoids flashing); price input debounced.
- **Consumes:** dev/contracts/domains/search.md (b7) + b6 trust badge / b5 variant reads.
- **Mocked client-side:** `services/search` via `searchMockApi` (**USE_SEARCH_MOCK=true, primary**) — b7's
index row + b5/b6 reads don't yet expose nurse name/avatar/distance or an aggregated profile
(name/bio/specialties/services list/latest review). Real `searchClientApi` maps what exists; swap is one
line once REQ-012 lands. Recorded in the phase report (not mocks-registry — that's for backend DI seams).
- **Gate:** npm run check green · npm run test:ci green (165 tests, +8). `npm run build` compiles + types
clean; prerender fails only on the pre-existing f5 `/nurse/verification` "Missing .env variable!" (needs
env set — unrelated to f6).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-012: search row name/avatar/distance +
`GET nurses/{id}/profile` aggregation).
## frontend-phase-5-b6 — Nurse verification flow (trust engine) — 2026-07-09
- **Shipped:** `services/verification` domain (types/keys/constants/validation/apis[client+mock(primary)+seam]/
hooks/index) — ONE cached `status()` query drives B3+B6, every mutation invalidates it. The nurse
verification route subtree `nurse/verification/{page(B3),identity(B4),credentials(B5),review(B6)}` +
co-located `VerificationChecklist` / `verificationSteps` (data-driven step rendering, synthetic mobile step).
Two shared components with tests: `<DocumentUpload>` (client type/size validation, progress %, success/
retry, re-upload-on-reject, server-metadata truth + local-capture mode) and `<TrustBadge>` (verified/
unverified/expired off `--bal-*`, reused by f6). Publish gate wired on `nurse/services` (`PublishGate` —
disabled until `approved`); trust badge on the nurse profile (own state from `ownBadgeState`). 6 new AppIcons,
4 route constants, `verification` i18n namespace (123 keys) in both locales. Honest copy: manual steps
never claim an automated authority check; a step reads "تاییدشده" only when `passed`.
- **Consumes:** dev/contracts/domains/verification.md + openapi/swagger.v1.json (backend-phase-6). Wire is
**camelCase**; action-style routes under `api/v1/nurse_verification/*` + `api/v1/nurses/{id}/trust_badge`.
- **Mocked client-side:** `services/verification` via `verificationMockApi` behind `USE_VERIFICATION_MOCK`
(**default true** — runs the whole journey standalone incl. a dev-only admin-decision sim, since b6 isn't
reachable here). One-line swap to `verificationClientApi`. See mocks-registry.
- **Gate:** npm run check green · npm run test:ci green (157 tests, +9) · en/fa in sync (123 verification keys).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-011 nurse credential-details endpoint).
## frontend-phase-4-b5 — Catalog browse (Home A5) & nurse service builder (B7) — 2026-07-05
- **Shipped:** `services/catalog` domain (types/keys/constants/apis[client+mock+seam]/hooks/index) — the b5
catalog skeleton + nurse pricing layer. Hooks: `useServiceCategories`, `useCategoryOptionGroups` (both
**session-cached reference data**, Infinite `staleTime`), `useMyVariants` (paginated, self-scoped),
`useCreateVariant`/`useUpdateVariant`/`useSetVariantActive` (mutations invalidate `catalogKeys.myVariantsLists()`;
update `setQueryData`s the row). Shared composites (each tested): **`CategoryTile`** (data-driven Home tile +
builder `selected` state), **`PriceDisplay`** (money-util Toman + i18n unit label + unit-aware estimated total),
**`VariantCard`** (offering card, active/deactivated distinction, no delete). Money util gained `tomanToRial`
(field-boundary Toman→IRR) + `multiplyIrr` (integer-safe estimate). Screens: **customer Home (A5)** — greeting +
avatar, search bar (navigates toward f6 `/search`, results deferred), **data-driven category grid**
(loading/empty/error), patient nudge (reuses cached f2 `usePatients`, no new fetch); **nurse Services & prices
(B7)** at `/nurse/services` (new sidebar tab) — offerings list (active/inactive, edit, soft deactivate w/ confirm,
reactivate, empty/skeleton) + **3-step variant builder** (category → required/optional options → price+unit+duration;
required-group gate; Toman→IRR digit-string submit; live unit-aware total; editable auto `displayName`; inline
`409` duplicate warning; locked-category edit form). Added `catalog`/`services`/`search` i18n namespaces + `home`
additions + `nav.services` (both locales); 7 icons (`services`,`category`,`elderly`,`post_surgery`,`infant`,
`chronic`,`companionship`); routes `SEARCH`,`NURSE_SERVICES`; deferred `/search` placeholder stub (→ f6).
- **Consumes:** dev/contracts/domains/catalog.md (backend-phase-5). Routes `api/v1/catalog/{categories,option_groups}`,
`api/v1/nurse_variants/{create,update/{id},set_active/{id},list,get/{id}}`. Wire camelCase; `price_unit` enum;
IRR-string money; `409` duplicate listing / `400` missing required dimension.
- **Mocked client-side:** `services/catalog` via `catalogMockApi` behind `USE_CATALOG_MOCK` (default `true`) — seeds
the 5 real b5 categories + representative option groups (incl. a cross-category one) + the `409`/`400` rules;
variant store seeded **empty** so the offerings empty-state demos. Real `catalogClientApi` wired for a one-line
flip. See mocks-registry + the report (note: the mock seeds option groups the fresh backend does not — an admin
authors them).
- **Gate:** npm run check green · npm run test:ci green (147 tests, +18 across 4 suites: catalog components + money)
· npm run build green with NEXT_PUBLIC_API_URL set (routes /nurse/services, /search generated; home prerenders).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-010 confirm the list pagination query-param name
`pageSize` vs the doc's `page_size`).
## frontend-phase-3-b4 — Addresses, map picker & nurse coverage areas — 2026-07-03
- **Shipped:** three domain services — `services/geography` (cached province→city→district reference lookups;
**Infinity `staleTime`** + shared `geographyKeys`; `useProvinces`/`useCities`/`useDistricts`; seam+mock+client),
`services/addresses` (address book CRUD + set-primary; single-primary invariant; every mutation invalidates
the list; `useAddresses`/`useCreateAddress`/`useUpdateAddress`/`useDeleteAddress`/`useSetPrimaryAddress`),
`services/serviceAreas` (coverage add/remove; `areaExists` dup-guard; `useServiceAreas`/`useAddServiceArea`/
`useRemoveServiceArea`). Shared composites (`src/components/geography/`, each tested): `CascadingRegionSelect`
(drives the cached cascade), `AddressMapPicker` (map-pin **stand-in** emitting real lat/lng), `AddressForm`,
`AddressCard`. Screens: **customer address book** (`/addresses`, reached from a profile-hub link — cascade +
map pin dialog, set-primary, delete, empty/skeleton), **nurse coverage editor** (`/nurse/coverage`, new
sidebar tab — chips, whole-city/specific-district scope toggle, inline duplicate block + 409, "won't appear
in search" empty warning). Added `geo`/`address`/`coverage` i18n namespaces + `nav.coverage` (both locales);
`location`/`delete`/`coverage` icons; routes `ADDRESSES`/`NURSE_COVERAGE`.
- **Consumes:** dev/contracts/domains/geography-addresses.md (backend-phase-4). Routes `api/v1/geo/{provinces,
cities,districts}`, `api/v1/customer_addresses/{list,create,update,set_primary,delete}`, `api/v1/
nurse_service_areas/{list,add,remove}`. Wire camelCase; geo query params snake_case; 409 on duplicate coverage.
- **Mocked client-side:** `services/geography` (`USE_GEOGRAPHY_MOCK`), `services/addresses` (`USE_ADDRESSES_MOCK`),
`services/serviceAreas` (`USE_SERVICE_AREAS_MOCK`) — all default `true`; real clients wired for a one-line flip.
The `AddressMapPicker` is a stand-in (no real map tiles). See mocks-registry + the report.
- **Reviewed:** 5-dimension adversarial review → 3 findings fixed (map marker RTL transform; `page_size`→`pageSize`
pagination casing on the real list calls; coverage "districts" dead-end on a district-less city).
- **Gate:** npm run check green · npm run test:ci green (129 tests, +17 across 5 suites) · npm run build green
with NEXT_PUBLIC_API_URL set (routes /addresses, /nurse/coverage generated).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-008 accept the map pin on address create/update,
REQ-009 `provinceId` on `CustomerAddressDto` for edit prefill).
## frontend-phase-2-b3 — Onboarding & profiles (customer, patient, nurse, bank) — 2026-07-02
- **Shipped:** three domain services — `services/patients` (rewritten to the b3 `PatientDto` + client-augmented
`relation`/`conditions`; full CRUD seam + mock + real client; `usePatients`/`useCreatePatient`/
`useUpdatePatient`/`useArchivePatient` with optimistic soft-archive), `services/profiles` (customer + nurse
profile get/upsert + avatar; `useCustomerProfile`/`useUpsertCustomerProfile`/`useNurseProfile`/
`useUpsertNurseProfile`/`useUploadAvatar`), `services/nurse` (bank accounts; `useNurseBankAccounts` with
pending-only `refetchInterval`, `useAddNurseBankAccount`, `useSetPrimaryBankAccount`; `iban.ts` Sheba
validate/normalize/bank-name). Screens: **A3→A4 onboarding wizard** (`(customer)/onboarding`), **E1 patients
list/CRUD** (add/edit dialog reusing the A4 form, soft-archive confirm, empty + skeleton states), **A5 Home**
(first-login redirect into onboarding when 0 patients + "complete patient record" nudge), **customer profile**
(name + preferred language + emergency contact, no national-ID), **nurse profile bootstrap** (avatar + bio +
years, unverified "not bookable" placeholder → verification), **nurse bank settings** (IBAN form + the three
ownership states pending/verified/mismatch). Shared composites `GenderToggle`/`ConditionChips`/`RelationSelect`/
`PatientForm`/`PatientCard`/`BankStatusPanel` (each tested); reused the f0 `StepperHeader`/`StatusChip`/
`PhoneNumberField`. Added `onboarding`/`home`/`profile`/`nurseProfile`/`bank` i18n namespaces + `patients`
extensions (both locales); `--bal-primary-soft` token (both schemes); nurse sidebar gains Profile + Bank.
- **Consumes:** dev/contracts/domains/identity-profiles.md (backend-phase-3). Routes `api/v1/{customer_profiles,
nurse_profiles}/{me,upsert}`, `api/v1/patients/{list,get,create,update,archive}`, `api/v1/nurse_bank_accounts/
{list,add,set_primary,verify_ownership}`.
- **Mocked client-side:** `services/patients` (`USE_PATIENTS_MOCK`), `services/profiles` (`USE_PROFILES_MOCK`),
`services/nurse` (`USE_NURSE_BANK_MOCK`) — all default `true`; real clients wired for a one-line flip. See
mocks-registry + the report for exactly what/why (relation/conditions, avatar, customer name/language gaps).
- **Gate:** npm run check green · npm run test:ci green (112 tests, +17) · npm run build green with
NEXT_PUBLIC_API_URL set (routes /onboarding, /nurse/profile, /nurse/bank generated).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-005 patient relation/conditions, REQ-006 avatar
upload route, REQ-007 customer name/preferred-language).
## frontend-phase-1-b2 — Auth: phone-OTP login & role routing — 2026-07-02
- **Shipped:** `services/auth` rewritten for phone-OTP (types/keys/apis[client+mock+seam]/hooks:
`useRequestOtp`/`useVerifyOtp`/`useMe`/`useRefresh`/`useLogout`/`useSelectRole`/`useSessionRoleSync`) —
the username/password stub is gone; A1/A2 customer login + B1/B2 nurse switch (one OTP flow parameterised
by intended role) at `/login`; the **role router** (`RoleRouter` + pure `resolveRoleDestination`) →
customer→family, nurse→nurse app, no-role→`/select-role`, admin→admin console (splash while `/me` loads,
no wrong-shell flash); `SelectRole` screen at `/select-role`; **silent token refresh** in the fetch layer
(single-flight `attemptTokenRefresh`, one retry on 401); widened `AuthState` (roles via `SessionUser`,
hydrated from `/me` by `useSessionRoleSync` in the private layout); `auth` i18n namespace + `common.brand*`
in both locales.
- **Consumes:** dev/contracts/domains/identity-auth.md + openapi/swagger.v1.json (backend-phase-2). Wire is
**camelCase**; routes `api/v1/auth/{request_otp,verify_otp,refresh,logout}`, `api/v1/me`, `api/v1/me/select_role`.
- **Mocked client-side:** `services/auth` via `authMockApi` behind `USE_AUTH_MOCK` (**default false** — b2 is
live; flip true for offline dev, dev code `123456`). See mocks-registry.
- **Gate:** npm run check green · npm run test:ci green (95 tests, +23) · npm run build green with
NEXT_PUBLIC_API_URL set. Fixed the jest `@/`→`src` alias (was `<rootDir>/$1`).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-002 OTP length/expiry, REQ-003 verify
error codes + lockout retry-after, REQ-004 multi-role `activeRole?`).
## frontend-phase-0 — Foundations: app shells, design system & data/contract patterns — 2026-07-02
- **Shipped:** 3 actor shells (customer bottom-nav / nurse / admin sidebar) + role-aware routing under
`(private-routes)`; `useActorRole`; the `services/{domain}` reference (`patients`, mocked behind a
seam) with deliberate Query caching + invalidation; `lib/api/types.ts` (envelope/pagination); money +
Shamsi-date utils; shared composites `OtpInput`/`PhoneNumberField`/`StepperHeader`/`StatusChip`/
`PlaceholderScreen` (each tested); i18n `nav`/`common`/`shell`/`patients` in both locales. Removed the
demo scaffolding; fixed the `BottomBar` pathname bug.
- **Consumes:** dev/contracts/conventions/* + openapi/swagger.v1.json (b0 = ping only). No feature
contract consumed yet.
- **Mocked client-side:** `services/patients` via `patientsMockApi` (USE_PATIENTS_MOCK=true) — template
for f1+. Swap is one line once real endpoints land.
- **Gate:** npm run check green · npm run test:ci green (72 tests) · npm run build green with
NEXT_PUBLIC_API_URL set.
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-001).