Files
baya-monorepo/dev/phases/frontend/frontend-phase-7-b8.md
T
2026-06-28 21:59:59 +03:30

338 lines
27 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 Phase 7 — Booking request flow (customer request + nurse inbox)
> **Mission:** turn a nurse profile into a *sent request* and close the request loop on both sides. The
> customer fills the **request form** (C4) — patient, service variant, address, date/time, and the
> first-class **caregiver-gender preference** — and lands on the **awaiting-acceptance** screen (C5) with
> a live countdown to the nurse's response deadline and a 3-step status tracker. The nurse opens an
> **incoming-requests inbox**, sees a request showing *only* the customer's notes (two-stage clinical
> disclosure), and accepts or rejects it. On accept, the customer flips to a 30-minute **payment-deadline**
> countdown that hands off to checkout (f9). This is the money-free request phase — no payment, no booking
> row yet — and it is where the platform's trust contract (same-gender match, deadlines, terminal states)
> becomes visible to both actors.
>
> **Track:** frontend · **Depends on:** [frontend-phase-6-b7](./frontend-phase-6-b7.md) (discovery: search/results/nurse profile) · [frontend-phase-3-b4](./frontend-phase-3-b4.md) (addresses & map picker) · backend **b8** contract ([booking-requests.md](../../contracts/domains/booking-requests.md)) · **Unlocks:** [frontend-phase-8-b9](./frontend-phase-8-b9.md) (booking detail · sessions · EVV)
> **Before you start, read [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md).** It is not optional.
---
## 1. Context — where this sits
We are at the hinge of the customer journey: discovery is done (f6), the customer is looking at a nurse
profile (C3) and taps **درخواست رزرو**. This phase builds the *request phase* of the booking lifecycle —
the deliberately money-free intent that lives in `booking_requests` and **only** becomes a `bookings` row
later, after the nurse accepts *and* payment captures (f9 / b9). Nothing here touches money or creates a
booking. The product framing: a family requests a specific nurse for a specific patient at a specific
address and time, the nurse retains accept/reject autonomy (a deliberate worker-classification stance),
and both sides see frozen deadlines so the engagement can't hang forever.
**What already exists (do not rebuild):**
- **f0 foundations** ([frontend-phase-0](./frontend-phase-0.md)): the three actor shells (customer
mobile + 5-tab bottom nav, nurse shell, admin), the `services/{domain}` + TanStack Query caching
pattern (template = `src/services/auth/*`: `types.ts` / `keys.ts` / `apis/clientApi.ts` /
`hooks/use*.ts` / `index.ts`), `clientFetch`/`serverFetch` + `ApiError` (`@/lib/api`), the
contracts→types convention, the money/Shamsi format utils in `src/utils/`, and the shared composites
(stepper/progress header, status chip, OTP/phone inputs). **Reuse these — do not re-create the
pattern.**
- **f3 addresses** ([frontend-phase-3-b4](./frontend-phase-3-b4.md)): the customer **address book**,
the **map picker**, and the cascading province/city/district selectors, all behind
`services/addresses` (or the f3 domain name). The request form **reuses** the address picker/list — it
does not build a new one. Patient `customer_addresses` already carry coordinates from f3's geocode.
- **f2 onboarding** ([frontend-phase-2-b3](./frontend-phase-2-b3.md)): the **patient** list/CRUD behind
`services/patients`. The request form's patient selector **reads** that list; it does not add a new
patient-creation path (link out to the f2 "add patient" flow for the empty case).
- **f4 catalog** ([frontend-phase-4-b5](./frontend-phase-4-b5.md)): a nurse's **service variants**
(`nurse_service_variants` — name, price unit `per_hour`/`per_session`/`per_half_day`/`per_day`/`per_24h`,
price). The service-type selector **reads** the chosen nurse's published variants.
- **f6 discovery** ([frontend-phase-6-b7](./frontend-phase-6-b7.md)): search (C1), results (C2), and the
**nurse profile (C3)** with its **درخواست رزرو** CTA. This phase is the destination of that CTA — wire
the navigation from C3 into the request form, passing the `nurse_id` (and optionally a pre-selected
variant).
> **Money/booking note:** there is **no payment and no `bookings` row** in this phase. The "pay & confirm"
> step (C6 summary, escrow notice, card/BNPL) is **(DEFERRED → [f9](./frontend-phase-9-b10.md))**. Booking
> detail, sessions, and EVV are **(DEFERRED → [f8](./frontend-phase-8-b9.md))**. Build only up to the two
> countdowns (response deadline, then payment deadline) and the hand-off CTA into checkout.
## 2. Required reading (do this first)
- [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md) and
[`../_shared/frontend-conventions-checklist.md`](../_shared/frontend-conventions-checklist.md).
- [`client/CLAUDE.md`](../../../client/CLAUDE.md) in full — the RSC/client boundary, layouts (never above
`[locale]`), i18n, theme/tokens, cookies, the `services/{domain}` fetch pattern, anti-patterns. Mirror
the **`auth`** service exactly when you create `services/bookingRequests`.
- **Invoke the `frontend-designer` skill** — mandatory for all visual work in this phase (C4 form, C5
awaiting screen + tracker, the nurse inbox list + detail). It is the brand/design contract: palette
(teal `#1d4a40`, terracotta `#d98c6a`, cream), tokens, typography, the `App*` library, the layout
shells, and the hard RTL/dark-mode rules. Do not hand-pick colours in `sx`.
- [`product/wireframes/index.html`](../../../product/wireframes/index.html) — the visual baseline. Study
**C3 → C4 → C5** and the nurse "نمای پرستار" framing. The screens this phase implements:
- **C4 · فرم درخواست** — patient selector (dropdown), service-type selector, address (map block, منزل),
date + time pickers, nurse-gender preference (**خانم / آقا / فرقی ندارد**). CTA: **ارسال درخواست**.
- **C5 · در انتظار تایید پرستار** — ⏳ status, "درخواست برای پرستار ارسال شد"; a summary card (nurse +
time); the 3-step tracker **درخواست ثبت شد → در انتظار تایید پرستار → پرداخت و تایید نهایی**; a
countdown to `nurse_response_deadline_at`. No CTA in the waiting state; on accept it shows the
payment-deadline countdown + a "continue to payment" CTA.
- **Nurse request inbox** — there is no dedicated wireframe panel, so design it consistently with the
nurse shell: a list of pending requests each with a per-request countdown, and a request-detail
showing **only** `customer_notes`, with accept / reject (reason) actions.
- [`product/business/05-booking-and-scheduling.md`](../../../product/business/05-booking-and-scheduling.md)
— the request→accept→pay→confirm lifecycle, the frozen deadlines, the two-table split, same-gender
matching, and the two-stage clinical-disclosure rule. **These are decisions, not guesses — read them.**
- **The contract you consume:** [`../../contracts/domains/booking-requests.md`](../../contracts/domains/booking-requests.md)
(from **backend-phase-8**) — the exact request/response shapes, routes, status codes, and enums.
Plus the shared conventions [`api-conventions.md`](../../contracts/conventions/api-conventions.md) and
[`money-and-types.md`](../../contracts/conventions/money-and-types.md) (envelope, snake_case routes,
pagination, enums-as-codes, UTC timestamps + Shamsi display, IRR-as-string, gender as load-bearing).
- The latest backend handoff `dev/shared-working-context/backend/handoff/after-backend-phase-8.md` — what
b8 shipped, which endpoints are live, and what (if anything) is still mocked server-side.
- The f6/f3 frontend reports in `dev/shared-working-context/reports/` — to reuse the patient/address/
variant query keys and the discovery navigation rather than re-fetching or re-deriving them.
## 3. Scope — build this
### 3.1 The `services/bookingRequests` domain (consume b8)
Create `src/services/bookingRequests/` by copying the `auth` template structure exactly:
- **`types.ts`** — string-literal union types mirroring the **booking-requests.md** contract (do **not**
guess shapes). At minimum:
- `RequiredCaregiverGender = 'male' | 'female' | 'any'` (the wire codes behind خانم/آقا/فرقی ندارد).
- `BookingRequestStatus = 'pending_nurse_response' | 'accepted_awaiting_payment' | 'rejected_by_nurse'
| 'expired_no_response' | 'payment_deadline_expired' | 'converted'`.
- `BookingRequestDto` (id, `nurse_id`, `patient_id`, `nurse_service_variant_id`, `customer_address_id`,
`scheduled_start_at` (UTC), `required_caregiver_gender`, `customer_notes`, `status`,
`nurse_response_deadline_at` (UTC), `payment_deadline_at` (UTC, nullable until accept),
`nurse_rejection_reason` (nullable), plus the display fields the contract returns — nurse name/avatar,
variant name + price-unit, patient name, address label). Money values (variant price) are **IRR digit
strings**, parsed via the f0 money util — never floats.
- `CreateBookingRequestPayload`, `RejectBookingRequestPayload` (reason).
- **`keys.ts`** — a query-key factory: `bookingRequestKeys.lists(role, statusFilter)`,
`bookingRequestKeys.detail(id)`, and the **nurse inbox** list key `bookingRequestKeys.nurseInbox(filter)`.
- **`apis/clientApi.ts`** — wrap `clientFetch` for each endpoint the contract defines (names from
**booking-requests.md** — expected, snake_cased per api-conventions):
- `POST .../booking_requests/create_booking_request` → create (customer).
- `GET .../booking_requests/list_booking_requests` → customer's requests, paginated, `status` filter.
- `GET .../booking_requests/get_booking_request` → one request (polled on C5).
- `GET .../booking_requests/list_nurse_requests` (or the contract's nurse-inbox route) → nurse's
incoming requests, paginated, default `status=pending_nurse_response`.
- `POST .../booking_requests/accept_booking_request` → nurse accept.
- `POST .../booking_requests/reject_booking_request` → nurse reject (with `nurse_rejection_reason`).
- **`hooks/` — one hook per file:**
- `useCreateBookingRequest.ts` (`useMutation`) — on success, navigate to C5 with the new id and
`setQueryData`/invalidate the customer list.
- `useBookingRequest.ts` (`useQuery`) — the C5 detail; **polls** while status is non-terminal
(`refetchInterval` ~ every 1530s while `pending_nurse_response` / `accepted_awaiting_payment`, and
**stops** on a terminal/`converted` status via a `select`/enabled guard) so the customer sees the
accept/reject/expire transition without a refresh.
- `useNurseRequestInbox.ts` (`useQuery`) — the nurse list, with light polling for new requests.
- `useAcceptBookingRequest.ts` / `useRejectBookingRequest.ts` (`useMutation`) — **invalidate the nurse
inbox list and the request detail** on success so the request leaves the pending list immediately.
- **`index.ts`** — barrel.
> If any shape the screens need is **missing** from booking-requests.md (e.g. the contract doesn't return
> the nurse's display name on the request DTO, or omits the price-unit), **append the gap** to
> `dev/shared-working-context/frontend/requests/for-backend.md` and **mock that field behind the
> `services/bookingRequests` clientApi seam** meanwhile (operating-rules §6). Record the mock in your
> report so it swaps out cleanly when b8 fills the gap. Never edit backend files.
### 3.2 C4 — the request form (customer)
A page under the customer shell (e.g. `(private-routes)/<customer-segment>/booking-requests/new`,
reachable from the C3 **درخواست رزرو** CTA with the `nurse_id`). RTL-first, mobile. Fields:
- **Patient selector** — a dropdown reading `services/patients` (f2). Empty state → a CTA linking to the
f2 "add patient" flow (don't inline patient creation here). The selected `patient_id` is sent.
- **Service-type selector** — reads the chosen nurse's `nurse_service_variants` (f4). Each option shows
the variant name + formatted price + price-unit label (i18n key off the `per_*` code, **not** a
hardcoded label). Sends `nurse_service_variant_id`.
- **Address block** — **reuse the f3 address picker / map block** (منزل/home), selecting a
`customer_address_id` from the address book (with the map preview). Do not rebuild the picker.
- **Date + time pickers** — produce a single UTC `scheduled_start_at` on the wire; **display** Shamsi via
the f0 date util. (Recurring/multi-session scheduling UI is **(DEFERRED → later)** — one start time here;
`session_count` is a server/booking concern.)
- **Nurse-gender preference** — a 3-option segmented control: **خانم (female) / آقا (male) / فرقی ندارد
(any)** → `required_caregiver_gender`. This is a **first-class field**, never a hidden default; if the
nurse's profile already fixes a gender, still send the explicit code the customer chose.
- **Request-stage notes** — a free-text field mapped to `customer_notes`. Copy must make clear this is the
*only* thing the nurse sees before accepting (it is **not** the clinical care record, which is
post-confirmation and **(DEFERRED → [f8](./frontend-phase-8-b9.md))**).
- **CTA: ارسال درخواست** — fires `useCreateBookingRequest`; loading state while the server computes/freezes
the deadline; on success navigate to C5. Surface domain `400`s (e.g. tenancy: patient/address not owned;
same-gender mismatch; variant not bookable) as field/form errors — but do **not** toast `401/403/5xx`
(the fetch layer already does).
Validate client-side at the boundary (all required fields chosen, future date) before enabling the CTA;
the server re-validates and is authoritative.
### 3.3 C5 — awaiting nurse acceptance + status tracker (customer)
A page keyed by the request id (e.g. `.../booking-requests/[id]`). Uses `useBookingRequest` (polling).
- **Header:** ⏳ "درخواست برای پرستار ارسال شد".
- **Summary card** — nurse (avatar + name), patient, service variant + price, address label, requested
time (Shamsi). Compose this as a **shared composite** (`src/components/...`) so the booking-detail screen
in f8 can reuse it (a "BookingRequestSummaryCard"); co-locate a `*.test.tsx`.
- **3-step status tracker** — reuse the **f0 stepper/progress header** composite:
1. **درخواست ثبت شد** (done as soon as the request exists),
2. **در انتظار تایید پرستار** (active while `pending_nurse_response`),
3. **پرداخت و تایید نهایی** (future; becomes active on `accepted_awaiting_payment`).
- **Countdown** — a `CountdownTimer` shared composite (`src/components/...`, co-located test) ticking down
to `nurse_response_deadline_at` (computed from the **server-supplied UTC instant** vs `Date.now()` — the
client never computes the deadline, only renders it). It is a pure presentational countdown; when it hits
zero, the UI shows "in expectation of server confirmation" and the poll resolves the real terminal
status. Use a single interval, cleaned up on unmount; do **not** re-render the whole page each tick
(isolate the ticking state in the timer component).
- **State transitions (driven by polled `status`):**
- `accepted_awaiting_payment` → swap step 2 to done, step 3 active; show **"✓ پرستار تایید کرد"**, a
prominent **30-minute payment countdown** to `payment_deadline_at`, and a CTA **"ادامه پرداخت ←"** that
navigates to checkout (**the checkout screen itself is [f9](./frontend-phase-9-b10.md) — wire the
route, stub the destination if f9 isn't merged**).
- `rejected_by_nurse` → terminal state card with the `nurse_rejection_reason` and a "request another
nurse" CTA back into discovery (f6).
- `expired_no_response` → terminal "no response in time" card + re-request CTA.
- `payment_deadline_expired` → terminal "payment window lapsed" card + re-request CTA.
- `converted` → the request became a booking → route to booking detail (**[f8](./frontend-phase-8-b9.md)**;
stub if not merged).
### 3.4 Nurse request inbox + detail (nurse)
Under the **nurse shell** (the wireframe's "نمای پرستار"), e.g. `(private-routes)/<nurse-segment>/requests`.
- **Inbox list** (`useNurseRequestInbox`) — pending requests, each row a card: patient first name/age,
service variant, requested time (Shamsi), the **required-caregiver-gender** chip, and a **per-request
countdown** to that request's `nurse_response_deadline_at`. Empty state: "درخواست جدیدی ندارید". Paginated
(page/page_size per api-conventions). Light polling so new requests appear.
- **Request detail** — shows the request summary **and only `customer_notes`** as the clinical context.
**It must never render `booking_care_instructions` or any encrypted clinical field** — those don't exist
pre-accept and are out of this contract; rendering them would break two-stage disclosure. Actions:
- **Accept** (`useAcceptBookingRequest`) — on success the request moves to `accepted_awaiting_payment`,
a `payment_deadline_at` is set server-side, and the customer's C5 (via its poll) starts the 30-min
payment countdown. Invalidate the inbox list + this detail.
- **Reject** (`useRejectBookingRequest`) — a small reason dialog capturing `nurse_rejection_reason`;
on success the request leaves the inbox. Invalidate the inbox list + detail.
- Both actions are disabled / show a terminal banner if the request already expired (the server returns
`409`/`400` for a stale accept/reject — surface it gracefully, then refetch).
### 3.5 i18n + tokens
Add a **`booking`** (and/or `bookingRequests`) namespace to **both** `messages/en.json` and
`messages/fa.json`, in sync, RTL-first. Every visible string is a key — the gender labels (خانم/آقا/فرقی
ندارد), the three tracker steps, the price-unit labels (off the `per_*` codes), all terminal-state copy,
countdown labels, and the empty states. Colours from `tokens.css` only; financial/terracotta accent (e.g.
the payment-deadline countdown) uses the brand terracotta token, not a literal.
## 4. Mocks & seams in this phase
This phase **introduces no new external seam** — booking requests carry **no money** and call no third
party. It only consumes the b8 HTTP contract.
- **Backend-not-ready / contract-gap fallback:** if `after-backend-phase-8.md` shows b8 isn't merged, or
booking-requests.md is missing a shape, build a **mock `clientApi`** behind the `services/bookingRequests`
seam (same function signatures the real one will have), driving a small in-memory state machine so the
whole flow is demoable: create → (timer or manual) accept/reject/expire. Record it in
[`mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md) and your report; swapping
to the real `clientApi` must be a one-file change. File any contract gap in
`dev/shared-working-context/frontend/requests/for-backend.md` (never edit backend files).
- **Reused seams:** the patient list (`services/patients`, f2), the address picker (`services/addresses`,
f3), and the nurse variants (`services/catalog` or f4's name) — **reuse**, do not redefine.
## 5. Critical rules you must not get wrong
- **Two-stage clinical disclosure.** The nurse sees **only** `customer_notes` before accepting — never any
encrypted `booking_care_instructions` or other clinical detail. That data isn't in this contract and must
not appear anywhere in the inbox/detail UI. Full care instructions are post-confirmation and belong to
[f8](./frontend-phase-8-b9.md).
- **`required_caregiver_gender` is a first-class field.** Always sent explicitly (`male`/`female`/`any`),
never defaulted or dropped — it drives same-gender bodily-care matching. The server re-validates; surface
a mismatch `400` clearly.
- **No money, no booking row here.** This is the request phase. Do not render a price-breakdown/escrow/pay
step (that's C6 / [f9](./frontend-phase-9-b10.md)) and do not assume a booking exists until `converted`.
- **Deadlines come from the server, frozen.** Render countdowns from the server-supplied UTC instants
(`nurse_response_deadline_at`, `payment_deadline_at`) against `Date.now()`; the client **never computes
or recomputes** a deadline. Show the **response** countdown pre-accept and the **30-minute payment**
countdown post-accept; show the correct **terminal** state (`rejected_by_nurse` / `expired_no_response`
/ `payment_deadline_expired`) when the poll resolves it.
- **Invalidate on accept/reject.** A nurse action must invalidate the inbox list + the request detail so
the request leaves the pending list immediately and the customer's polled C5 reflects it — never leave
stale cache. Equally, don't over-poll: stop polling once a terminal/`converted` status is reached.
- **Minimal re-renders.** The ticking countdown state is isolated in the timer component (not lifted to a
page that would re-render the form/summary every second). Stable query keys, `select` for slices.
- **RTL + both locales + tokens + MUI primitives.** `fa` default & RTL; every string in `en.json` and
`fa.json` in sync; colours from `tokens.css`; MUI v9 primitives/`App*` reused, shared composites
(summary card, countdown) at the shared level with co-located tests — never re-implement a root primitive
and never bury a reusable composite in a page.
## 6. Definition of Done
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
- [ ] `services/bookingRequests` exists (types/keys/apis/hooks/index), typed **from** booking-requests.md
(gaps filed + mocked behind the seam, not guessed).
- [ ] **C4 request form** submits a valid request (patient + variant + address + date/time + gender +
notes) and navigates to C5; client-side validation gates the CTA; domain `400`s surface as
form/field errors.
- [ ] **C5 awaiting screen** shows the summary card, the 3-step tracker, and a live countdown to
`nurse_response_deadline_at`; it transitions (via poll) through accept (→ 30-min payment countdown +
checkout CTA), reject, and both expiry terminal states.
- [ ] **Nurse inbox** lists pending requests (with per-request countdown + gender chip), the detail shows
**only `customer_notes`**, and accept/reject work and **invalidate the inbox + detail**.
- [ ] Polling stops on terminal/`converted` status; no needless refetch; the ticking countdown doesn't
re-render the whole page.
- [ ] `booking`/`bookingRequests` i18n keys added to **both** locales in sync; colours from tokens; RTL
verified.
- [ ] `npm run check` green; `npm run test:ci` green for the new shared composites (summary card,
countdown timer) and any touched shared component.
- [ ] `client/CLAUDE.md` *Project Structure* updated for the new route segments + the `services/bookingRequests`
domain and any new shared components.
## 7. How to test (what a human can verify after this phase)
Run `npm run dev` (point `NEXT_PUBLIC_API_URL` at a b8 server, or use the mock `clientApi` seam if b8
isn't merged). Then:
1. **Submit a request.** From a nurse profile (C3) tap **درخواست رزرو** → on C4 pick a patient, a service
variant, an address (map block), a future date/time, and a gender preference (خانم/آقا/فرقی ندارد),
add a note → **ارسال درخواست**. *Expected:* land on **C5** showing the summary card, the 3-step tracker
(step 2 active), and a countdown ticking down to the nurse's response deadline.
2. **Nurse sees it.** In the nurse shell open **requests** (inbox). *Expected:* the new request appears
with the patient/variant/time, the **required-gender chip**, and a per-request countdown; opening the
detail shows **only the `customer_notes`** — no clinical/care fields anywhere.
3. **Nurse accepts.** Tap accept. *Expected:* the request leaves the pending inbox immediately
(cache invalidated); on the customer's **C5** (without a manual refresh, via the poll) step 2 flips to
done, step 3 activates, a **✓ پرستار تایید کرد** badge appears, the **30-minute payment countdown**
starts, and the **ادامه پرداخت ←** CTA appears (routing toward checkout/f9).
4. **Reject path.** On a different request, nurse rejects with a reason. *Expected:* customer's C5 shows the
terminal **rejected** card with the reason + a re-request CTA back into discovery.
5. **Expiry paths.** Let (or simulate via the mock) the response deadline lapse → C5 shows
**expired_no_response**; let the payment window lapse after accept → C5 shows **payment_deadline_expired**.
Both are terminal with a re-request CTA.
6. **Quality:** locale switch flips `dir` + strings on every screen; dark mode holds; `npm run check` and
`npm run test:ci` pass; React Query Devtools shows the inbox/detail invalidating on accept/reject and the
poll stopping at a terminal status.
## 8. Hand off & document (close the phase)
- **Docs:** update `client/CLAUDE.md` *Project Structure* (the new customer + nurse route segments, the
`services/bookingRequests` domain, the new shared composites — `BookingRequestSummaryCard`,
`CountdownTimer`). Fix any doc drift you touch. If you discover/decide a request-flow rule the
`product/` docs don't capture (e.g. the exact tracker wording, the re-request UX), note it in
[`product/business/05-booking-and-scheduling.md`](../../../product/business/05-booking-and-scheduling.md)
(don't invent rules — record decisions) and regenerate the HTML view per `product/CLAUDE.md` if you
edited Markdown.
- **Contracts:** this phase **consumes** [`../../contracts/domains/booking-requests.md`](../../contracts/domains/booking-requests.md)
— derive `services/bookingRequests/types.ts` from it; produce no contract. Append any missing/ambiguous
shape to `dev/shared-working-context/frontend/requests/for-backend.md`.
- **Handoff & report:** append your phase summary to
`dev/shared-working-context/frontend/STATUS.md`; write
`dev/shared-working-context/reports/frontend-phase-7-report.md` covering *what was built* (C4/C5/nurse
inbox + the domain service), *what is now testable and exactly how* (the steps in §7), *what is mocked*
(any contract-gap field behind the `services/bookingRequests` seam + how it swaps to real), *contracts
consumed* (booking-requests.md) and any gaps filed, and *follow-ups* for f8/f9 (the `converted` → booking
detail handoff, the payment CTA → checkout handoff). Update
[`mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md) only if you added a
client-side mock seam.
- **Memory:** save a `project` memory note for the non-obvious decisions here — the dual-countdown design
(server-frozen deadlines, client renders only), the polling-until-terminal pattern for request status,
and the two-stage-disclosure boundary in the nurse inbox — with a one-line pointer in `MEMORY.md`.