add build development phases

This commit is contained in:
hamid
2026-06-28 21:59:59 +03:30
parent 1df3cd9f64
commit 53a40dc51d
52 changed files with 12379 additions and 0 deletions
+337
View File
@@ -0,0 +1,337 @@
# 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`.