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

344 lines
29 KiB
Markdown

# Frontend Phase 8 — Booking detail, sessions & nurse EVV
> **Mission:** turn an accepted-and-paid request into a living engagement on screen. Build the
> **booking-detail** view both actors share — a server-truth **status timeline**
> (`pending_payment → confirmed → in_progress → completed → disputed/closed/cancelled`) and the
> per-visit **session schedule** — plus the nurse's day-one operational surface: **EVV check-in/out**
> (capture GPS, post it, show the "ورود ثبت شد · موقعیت تایید شد (EVV)" banner) and the **care-instructions**
> read that is unlocked **only post-confirmation to the assigned nurse**. This is the screen where the
> trust promise (proof of service, two-stage clinical disclosure, escrow-then-release) becomes visible,
> and it is the launch pad for checkout (f9) and reviews/records (f13).
>
> **Track:** frontend · **Depends on:** [`frontend-phase-7-b8.md`](./frontend-phase-7-b8.md) (request flow + booking lists/status tracker) and the **b9** contract ([bookings-evv.md](../../contracts/domains/bookings-evv.md)) · **Unlocks:** checkout & payment (`frontend-phase-9-b10.md`), reviews & patient records (`frontend-phase-13-b14.md`)
> **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 **f8** of the frontend chain. The customer can now search, open a nurse, send a request, and
watch it move `pending_nurse_response → accepted_awaiting_payment` (built in f7); the nurse has an inbox
to accept/reject. The backend's **b9** phase has just landed the *booking* phase: a request that was
accepted **and** paid converts to a `bookings` row with `booking_sessions`, encrypted
`booking_care_instructions`, and per-session `visit_verifications` for EVV. This phase puts a face on all
of that. It is the hinge between "I asked for a nurse" and "a nurse is actually delivering care" — the
booking detail is where the customer watches the engagement progress and the nurse does their job.
Two product truths drive every decision here, and both are non-negotiable:
- **Two-stage clinical disclosure** (Principle 6): full care instructions are encrypted and revealed
**only after confirmation, only to the assigned nurse + admin**. The UI must *gate* this, never leak it.
- **EVV is proof of service** ([06-evv-and-service-delivery.md](../../../product/business/06-evv-and-service-delivery.md)):
the nurse clocks in/out **per session** with GPS; a location mismatch is **advisory** (a banner, never a
block); EVV check-out is what eventually makes a session payout-eligible (after the dispute window — that
gating lives server-side; we just render its result).
**What already exists (do not rebuild) — link and extend, never re-create:**
- **f0 foundations** ([`frontend-phase-0.md`](./frontend-phase-0.md)): the three actor app shells +
route groups, the `services/{domain}` + TanStack Query caching pattern (the `auth` service is the
template — `types.ts`/`keys.ts`/`apis/clientApi.ts`/`hooks/use*.ts`/`index.ts`), the money/format util
(`formatIrrToToman`, Shamsi date display), and shared composites (status chip, stepper/progress header,
cards). **Reuse the status-chip and the stepper/progress-header composites here** — do not build new ones.
- **f7 booking-request flow** ([`frontend-phase-7-b8.md`](./frontend-phase-7-b8.md)): the
`services/bookings` domain seam already exists with the **request** half (`CreateBookingRequest`,
request list/detail, the C5 awaiting-acceptance status tracker, the nurse request inbox). **This phase
extends the same `services/bookings` domain** with the booking/session/EVV half — same `keys.ts`
factory, same `clientApi.ts`, same hook conventions. Do **not** create a parallel domain.
- **The app chrome**: the customer mobile shell with the 5-tab bottom nav (the **رزروها/Bookings** tab is
the customer entry to booking detail), the nurse shell (where EVV and the day's schedule live), the
RSC root layout, themes/tokens, `clientFetch`/`serverFetch`, the cookie manager, `AuthContext` (roles),
the toast bridge. None of this is rebuilt.
> **Reminder:** backend phases own the contracts. If a shape you need (e.g. a session's `payout_eligible_at`,
> the care-instructions decrypt response, the GPS-match result) is missing from
> [bookings-evv.md](../../contracts/domains/bookings-evv.md), you **append a request** to
> `dev/shared-working-context/frontend/requests/for-backend.md` and **mock behind the `services/bookings`
> seam** meanwhile (operating-rules §6). You never edit backend files.
## 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) — how you
work and the tick-list (RSC boundary, `clientFetch`/services-only, Query caching + invalidation, minimal
re-renders, MUI primitives reused, i18n both locales, tokens, RTL).
- [`../../../client/CLAUDE.md`](../../../client/CLAUDE.md) in full — the engineering contract (layouts,
RSC/client boundary, i18n, theme, cookies, fetch services, anti-patterns). Trust the code over any stale
doc note (the f0 audit found small drift); fix the doc if you touch that area.
- **Invoke the `frontend-designer` skill** — this phase is heavy on visual surfaces (status timeline, a
session list with per-session state, the EVV check-in/out screen, the EVV success banner, the care
instructions card). **All visual work goes through the skill** (palette, tokens, typography, the `App*`
library, RTL rules). Brand: teal `#1d4a40`, terracotta `#d98c6a` for financial/EVV affordances — the
wireframe gives the nurse view a **terracotta border** ("نمای پرستار").
- [`../../../product/business/06-evv-and-service-delivery.md`](../../../product/business/06-evv-and-service-delivery.md)
— the EVV business rules: per-session GPS check-in/out, advisory address-match tolerance
(`evv_location_tolerance_meters`), no-show alerting (server-side), payout gated on EVV completion **and**
closed dispute window. **Read this before building the EVV screen** — the rules are decisions, not guesses.
- [`../../../product/wireframes/index.html`](../../../product/wireframes/index.html) — the visual baseline.
The screens this phase implements:
- **Booking detail + status timeline** — the both-roles view (C5 tracker grows into a full timeline;
states `pending_payment/confirmed/in_progress/completed/disputed/cancelled`), session list with
per-session schedule/status, money summary (gross/commission/tax labels).
- **E3 (top half)** — the **nurse EVV** surface: header "ویزیت امروز", the **check-in banner**
"ورود ثبت شد ۰۹:۰۲ · موقعیت تایید شد (EVV)", today's-task checklist awareness, and the **"ثبت خروج
(EVV)"** check-out action. *(The bottom half of E3 — the free-text visit-note authoring — and the full
E2 patient-record viewer are **(DEFERRED)** to [`frontend-phase-13-b14.md`](./frontend-phase-13-b14.md).)*
- **Care-instructions (post-confirm)** — the decrypted clinical/logistical context the assigned nurse
sees only after confirmation; in the customer flow this is the "Care details" surface authored after pay.
- [bookings-evv.md](../../contracts/domains/bookings-evv.md) — **the contract you consume.** It is the
source of truth for: the booking-detail response (status enum, the three money amounts, `session_count`,
`dispute_window_ends_at`), the session shape (`booking_session_id`, schedule, per-session `status`,
`visit_payout_amount`, `payout_eligible_at`), the EVV check-in/out request+response
(`check_in_address_match` advisory result, timestamps, status), and the gated care-instructions read.
**Do not guess these shapes** — derive `types.ts` from this doc; file any gap to the for-backend request
log and mock meanwhile.
- [`../../contracts/conventions/money-and-types.md`](../../contracts/conventions/money-and-types.md) +
[`api-conventions.md`](../../contracts/conventions/api-conventions.md) — the envelope, **IRR-as-string +
Toman display**, enums-as-codes, **UTC timestamps + Shamsi display**. The session schedule and EVV
timestamps are UTC; render them Shamsi. Money renders through the f0 `formatIrrToToman` util.
- The existing `services/auth/*` and the **f7 `services/bookings/*`** — copy their exact structure; you are
extending the latter, not starting fresh.
## 3. Scope — build this
Everything below lives under the **customer mobile shell** (booking detail, opened from the رزروها tab) and
the **nurse shell** (booking detail + the EVV/today surface). All server access goes through the
**`services/bookings`** domain (extended from f7). All money via `formatIrrToToman`; all timestamps Shamsi.
### 3.1 `services/bookings` — extend the domain (data layer)
Extend, do not replace, the f7 `services/bookings` files:
- **`types.ts`** — add, derived from [bookings-evv.md](../../contracts/domains/bookings-evv.md):
`BookingDetailDto` (id, status enum `pending_payment|confirmed|in_progress|completed|disputed|closed|cancelled`,
`gross_price_irr`/`balinyaar_commission_irr`/`nurse_payout_amount` as **IRR strings**, `session_count`,
`dispute_window_ends_at`, nurse/patient/address snapshot fields), `BookingSessionDto`
(`booking_session_id`, scheduled start/end, per-session `status` `scheduled|in_progress|completed|missed|cancelled`,
`visit_payout_amount`, `payout_eligible_at`), `VisitVerificationDto` (check-in/out timestamps,
`check_in_address_match` advisory boolean/score, `status`), `CareInstructionsDto` (decrypted conditions /
meds / allergies / instructions / emergency contact — **only present in the gated response**), and the
EVV command DTOs `CheckInVisitInput`/`CheckOutVisitInput` (`booking_session_id`, `latitude`, `longitude`,
client `captured_at`). Status enums are **codes**, mapped to i18n labels in the UI — never display the raw code.
- **`keys.ts`** — add to the existing factory: `bookingDetail(id)`, `bookingSessions(bookingId)`,
`sessionEvv(sessionId)`, `careInstructions(bookingId)`. Keep the f7 request keys.
- **`apis/clientApi.ts`** — add the calls (names follow the contract routes; b9 owns the exact paths):
`getBookingDetail(id)`, `listBookingSessions(bookingId)`, `getCareInstructions(bookingId)` (the gated
read), `checkInVisit(input)`, `checkOutVisit(input)`, `getSessionEvv(sessionId)`. Each wraps
`clientFetch`**never raw `fetch`**. Add a `serverApi.ts` `getBookingDetail` **only if** you prefetch
the detail in an RSC (recommended — see 3.5).
- **Hooks (one per file)** under `hooks/`:
- `useBookingDetail(id)``useQuery` on `bookingDetail(id)`; deliberate `staleTime` (detail changes on
status transitions — keep modest, e.g. 30s, and **invalidate on EVV mutations** so the timeline reflects
server truth immediately).
- `useBookingSessions(bookingId)``useQuery` on `bookingSessions(bookingId)`.
- `useCareInstructions(bookingId, { enabled })``useQuery`, **`enabled` gated** by `status === 'confirmed'
|| beyond` **and** the viewer being the assigned nurse or admin (see 3.4). When disabled it never fires.
- `useCheckInVisit()` / `useCheckOutVisit()` — `useMutation`; on success **invalidate** `bookingDetail`,
`bookingSessions`, and `sessionEvv` (so the timeline, the session row, and the EVV banner all reflect
the new server state — no manual optimistic money/status math). Surface only domain-specific 4xx
messages (e.g. "no open check-in" on check-out); let the fetch layer handle 401/403/5xx toasts.
### 3.2 Booking detail + status timeline (both roles) — `app` route + components
- **Route:** a booking-detail page under the existing `(private-routes)` structure, reachable from the
customer **رزروها/Bookings** list (built in f7) and from the nurse booking list. Keep it role-aware via
`AuthContext` — same page, role-conditioned sections (the EVV actions and care-instructions card render
for the **assigned nurse**; the customer sees the read-only timeline + sessions + money summary).
- **Status timeline** — a shared composite `BookingStatusTimeline` (in `src/components/booking/`,
co-located test) that renders the canonical order `pending_payment → confirmed → in_progress →
completed`, with terminal branches `disputed/closed/cancelled` shown distinctly. **It reflects server
truth** (`BookingDetailDto.status`) — never advance a step client-side. Reuse the f0 stepper/progress
header primitive underneath; this is the grown-up version of the f7 C5 3-step tracker.
- **Session schedule list** — a `SessionList` composite rendering `BookingSessionDto[]`, each row a
`SessionCard` showing Shamsi schedule, a per-session **status chip** (reuse the f0 status chip — map
`scheduled/in_progress/completed/missed/cancelled` to labelled, tokenised colours) and, for the assigned
nurse, the EVV CTA state (see 3.3). **A single-visit booking still renders exactly one session row** —
do not special-case it away; the data always has ≥1 session.
- **Money summary** — a small `BookingMoneySummary` showing gross / Balinyaar commission (کارمزد) / and the
nurse-payout split, each via `formatIrrToToman`. (Tax/مالیات line + the escrow notice are the **checkout**
surface — **(DEFERRED)** to [`frontend-phase-9-b10.md`](./frontend-phase-9-b10.md); here just show the
confirmed split. If the detail response omits a line, label what you have and don't fabricate.)
- **States:** loading (skeleton), the per-status content (confirmed shows upcoming sessions; in_progress
highlights the active session; completed shows the dispute-window note from `dispute_window_ends_at`;
cancelled/disputed terminal copy), and an empty/not-found guard if the id isn't the viewer's booking.
### 3.3 Nurse EVV check-in / check-out (E3 top) — nurse shell
- **Today / session-aware surface** — within the nurse booking detail (and reachable from the nurse "today"
context), render the **per-session EVV control**. CTA state machine driven by session + EVV data:
`scheduled` → **"ثبت ورود (EVV)"** (check-in); `in_progress` (checked-in, no check-out) → **"ثبت خروج
(EVV)"** (check-out); `completed` → done (show elapsed duration); `missed` → missed state. Today's-task
awareness: show the session's task context (the **task checklist body is read-only here** — authoring is
f13).
- **GPS capture** — a small client util `captureGeoPosition()` behind a DI-style seam
(`src/services/bookings/evv/locationProvider.ts` exposing `ILocationProvider`) wrapping the browser
Geolocation API. On check-in/out: acquire position (loading spinner "در حال دریافت موقعیت…"), then call
`useCheckInVisit`/`useCheckOutVisit` with `{ booking_session_id, latitude, longitude, captured_at }`.
**Permission-denied / unavailable is not a hard stop** — the product rule says mismatch is advisory, so
allow the nurse to proceed (submit without coords or with a flagged value per the contract) and show the
advisory banner; never block the visit on GPS.
- **The EVV check-in banner** — on a successful check-in (and whenever an open check-in exists), render the
wireframe banner verbatim in spirit: **"ورود ثبت شد {{time}} · موقعیت تایید شد (EVV)"** when
`check_in_address_match` is true, and the **advisory** variant **"ورود ثبت شد {{time}} · موقعیت خارج از
محدوده (در حال بررسی)"** when it is false — a tokenised warning banner, **not** an error, and it does
**not** block check-out. Build it as a shared composite `EvvStatusBanner` (co-located test) since both the
session card and the day surface use it. Time renders Shamsi/clock from the server `checked_in_at`.
- **Check-out** — requires an open check-in; if none, show the domain message (don't toast the fetch layer's
generic). On success the session row flips to `completed` (via the invalidations in 3.1) and shows elapsed
duration. **Do not compute or display payout-eligibility client-side** — `payout_eligible_at` and the
dispute-window gate are server truth; render whatever the detail/session response gives you.
### 3.4 Care-instructions read (two-stage disclosure) — gated card
- A shared `CareInstructionsCard` (in `src/components/booking/`, co-located test) that renders the decrypted
`CareInstructionsDto` (conditions / medications / allergies / instructions / emergency contact).
- **Gate the UI, hard:** the card and its query (`useCareInstructions`) render/fire **only when** the booking
is `confirmed` or beyond **and** the current user is the **assigned nurse** (or admin). For anyone else —
the customer, an unassigned nurse, a pre-confirmation viewer — the card is **not rendered and the query
never fires**. This is the client mirror of the server's gated `GetCareInstructions`; the server is the
real boundary, but **the UI must not even request** instructions it has no right to (a 403 from the server
is a defect path, not the design). Show a neutral "visible to your assigned nurse and support only"
affordance to the customer instead.
- The customer-side authoring of care details (the post-confirmation "Care details" form,
`SubmitCareInstructions`) is the **write** path — if b9's contract exposes it, build a minimal read-back
here; the full encrypted authoring form is owned by the booking write flow and may be **(DEFERRED)** to
f13 if the contract doesn't surface it in b9. Read the contract; if absent, mock the read and log the gap.
### 3.5 RSC prefetch (remove a client round-trip)
Where the booking detail is the first paint, prefetch `getBookingDetail(id)` in the RSC and hand it to the
client tree via `initialData` / a hydrated query (the f0 pattern). Keep the EVV mutations + care-instructions
on the client. Respect the RSC/client boundary (no `@/lib/cookies/client` in an RSC; no
`next-intl/server`/`next/headers` in a client component).
### 3.6 i18n + tokens
- Add the user-visible strings under the existing **`booking`** namespace (and reuse `common`/`nav`) in
**both** `messages/en.json` and `messages/fa.json`, in sync — status labels, session statuses, the EVV
banner strings (in-range + advisory out-of-range), check-in/out CTAs, GPS-acquiring text, the
care-instructions section labels + the customer "visible to your nurse only" copy, the dispute-window note.
`fa` is default + RTL — design RTL-first and verify mirroring (the timeline and session rows must read
right-to-left correctly).
- All colours from `tokens.css` (`var(--bal-…)`); the EVV/financial terracotta affordance via the brand
token, the advisory banner via the warning token, status chips via the status-chip tokens. No hardcoded
colours in `sx`. MUI **v9** API only; reuse `APP_THEME_LTR/RTL`.
**Out of scope (DEFERRED — pointers, do not build here):**
- Full **patient-record viewer (E2)** and **nurse visit-NOTE authoring (E3 bottom)** →
[`frontend-phase-13-b14.md`](./frontend-phase-13-b14.md).
- **Checkout / pay / escrow notice / invoice / tax line** → [`frontend-phase-9-b10.md`](./frontend-phase-9-b10.md).
- **Cancellation flow + refund/policy-fee disclosure** → [`frontend-phase-10-b11.md`](./frontend-phase-10-b11.md).
- **Admin EVV-review queue** (location-mismatch / no-show worklist) → admin console
[`frontend-phase-15-b15.md`](./frontend-phase-15-b15.md). This phase raises no alerts client-side; no-show
detection is a server job.
## 4. Mocks & seams in this phase
- **This phase introduces one client seam: `ILocationProvider`** (`src/services/bookings/evv/locationProvider.ts`)
— wraps the browser Geolocation API for EVV GPS capture. The real implementation calls
`navigator.geolocation.getCurrentPosition`; a **mock** returns canned coordinates so the EVV flow is
testable without a device and so a denied/unavailable path can be exercised. Record it in
`dev/shared-working-context/reports/mocks-registry.md` with the seam, what it fakes, the config key (e.g.
`NEXT_PUBLIC_EVV_MOCK_GPS`), and how to make it real. **Server-side** GPS/address-match math lives behind
the backend's geocoding/geo-distance seam — **reuse it from b9**, do not introduce a server seam here.
- **`services/bookings` data, if b9 isn't merged yet:** build the booking/session/EVV/care calls behind the
same `services/bookings` `clientApi` as a **mock `clientApi`** (real-shaped per the contract), select it
by config exactly as f0 established, and record it in your frontend report so it swaps cleanly when the
real endpoints land. The EVV mutations' mock should flip the mocked session/EVV state so the banner +
timeline transitions are demonstrable.
- **Everything else is reused:** `clientFetch`/`serverFetch`, the Query client, the cookie/auth manager, the
toast bridge, the f0 composites (status chip, stepper). Introduce no new cross-cutting seam beyond
`ILocationProvider`.
## 5. Critical rules you must not get wrong
- **Two-stage clinical disclosure is a UI gate, not just a server check.** Care instructions render and are
**queried only** when the booking is `confirmed`+ **and** the viewer is the assigned nurse or admin. An
unassigned user — including the customer and any other nurse — must **never** see the instructions and the
client must **never even fire the request**. Treat a 403 as a defect path, not the intended flow.
- **A GPS / address mismatch is advisory — a banner, never a block.** Out-of-range check-in still succeeds;
show the warning-tokened "موقعیت خارج از محدوده (در حال بررسی)" banner and let check-out proceed. Never
auto-cancel, never withhold the visit, never gate check-out on the match. Permission-denied/unavailable
GPS likewise must not block the nurse.
- **The status timeline reflects server truth.** `BookingDetailDto.status` is the single source — never
advance, infer, or optimistically jump a timeline step on the client. After an EVV mutation, **invalidate**
the detail/session queries and re-render from the server response.
- **Money is display-only here and never computed.** Render `gross_price_irr` / `balinyaar_commission_irr` /
`nurse_payout_amount` exactly as the server sends them (IRR **strings**, integer-safe), through
`formatIrrToToman`. Do not sum, derive, or re-split amounts on the client; the booking money identity
(`gross = commission + payout`) and per-session `visit_payout_amount` are computed and guaranteed
server-side. **Do not compute payout-eligibility** — `payout_eligible_at` is gated by the dispute window
server-side; one payout per booking is enforced server-side; render, don't recompute.
- **A single-visit booking still has exactly one session.** Render the one `booking_sessions` row through the
same `SessionCard` path; no "0 sessions" or single-visit special case.
- **Caching is a feature.** Set deliberate `queryKey`/`staleTime`; **invalidate on every EVV mutation** so
the timeline, session row, and EVV banner update without a full refetch storm. Don't refetch data already
in cache; prefer the RSC prefetch for first paint.
- **Boundaries & primitives:** respect the RSC/client boundary; fetch only through `services/bookings` →
`clientFetch`/`serverFetch`; MUI primitives stay MUI; the shared composites
(`BookingStatusTimeline`/`SessionCard`/`EvvStatusBanner`/`CareInstructionsCard`) live at the shared level
with co-located tests, not buried in a page. Minimise re-renders (stable refs, `select`, colocated state).
- **i18n + RTL + tokens:** every string in both locale files, in sync; `fa` default & RTL-correct; colours
from `tokens.css`; MUI v9 only. Invoke the **frontend-designer** skill for all visual work.
## 6. Definition of Done
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
- [ ] Booking detail renders for both roles from `useBookingDetail`, with a server-truth
`BookingStatusTimeline` covering all seven statuses and a `SessionList` (≥1 session, single-visit
included), money summary via `formatIrrToToman`.
- [ ] Nurse EVV: check-in captures GPS through `ILocationProvider`, posts via `useCheckInVisit`, and shows
the in-range **"ورود ثبت شد … موقعیت تایید شد (EVV)"** banner or the advisory out-of-range variant;
check-out via `useCheckOutVisit` requires an open check-in and flips the session to `completed`
(server-driven, via invalidation). GPS denial does not block.
- [ ] Care-instructions card + query are **gated**: visible/fired only for the assigned nurse (or admin) on a
`confirmed`+ booking; never rendered/requested for the customer or an unassigned user (verified by test).
- [ ] All new strings in `en.json` **and** `fa.json`, in sync; RTL verified; colours from tokens; MUI v9.
- [ ] `services/bookings` extended (not duplicated); hooks invalidate on EVV mutation; types derived from
[bookings-evv.md](../../contracts/domains/bookings-evv.md) (gaps logged to for-backend + mocked).
- [ ] The shared composites (`BookingStatusTimeline`, `SessionCard`, `EvvStatusBanner`, `CareInstructionsCard`)
each have a co-located `*.test.tsx`; `npm run check` green; `npm run test:ci` green.
- [ ] `client/CLAUDE.md` *Project Structure* updated for the new `src/components/booking/` folder, the
`services/bookings` extension, and the `ILocationProvider` evv seam.
## 7. How to test (what a human can verify after this phase)
Run `npm run dev` (with the b9 endpoints live, or the mock `clientApi` selected by config). Walk these:
1. **Open a confirmed booking** from the customer رزروها/Bookings tab → the page shows the **status timeline**
sitting at `confirmed`, the **session schedule** (single-visit shows exactly one session; multi-day shows
N), and the **money summary** in Toman. Switch locale → strings + `dir` flip correctly, timeline reads RTL.
2. **As the assigned nurse**, open the same booking → the **care-instructions card** is visible (conditions/
meds/allergies/instructions/emergency contact). **As the customer or an unassigned nurse**, the card is
absent and the network tab shows the care-instructions request **was never made**.
3. **Nurse check-in** on today's session → "در حال دریافت موقعیت…" spinner, then the **EVV banner** "ورود ثبت
شد ۰۹:۰۲ · موقعیت تایید شد (EVV)"; session chip → `in_progress`; timeline → `in_progress`. With the mock
GPS forced out-of-range, the banner shows the **advisory** out-of-range variant and check-in **still
succeeds**. Deny GPS permission → the nurse can still proceed.
4. **Nurse check-out** → session chip → `completed` with elapsed duration; the booking timeline advances to
`completed` from the server response (no client-side step jump). Check-out before any check-in → the
domain "no open check-in" message, no generic toast.
5. **Caching:** in React Query Devtools, the EVV mutation **invalidates** `bookingDetail`/`bookingSessions`/
`sessionEvv` and the UI re-renders from the refetch; revisiting the page within `staleTime` does not
refetch.
6. `npm run check` and `npm run test:ci` pass; the gating test proves the customer/unassigned path never
renders or requests care instructions.
## 8. Hand off & document (close the phase)
- **Docs:** update the **Project Structure** tree in [`client/CLAUDE.md`](../../../client/CLAUDE.md) for
`src/components/booking/`, the extended `services/bookings`, and the `services/bookings/evv` location
seam; add a short note that the booking-detail/EVV pattern (timeline + sessions + gated care + EVV banner)
is the template f9/f13 extend. Fix any doc drift you touch. If you discover/confirm a business rule the
`product/` docs don't capture (e.g. the exact advisory-banner copy or the post-confirm disclosure timing),
record it in [`product/business/06-evv-and-service-delivery.md`](../../../product/business/06-evv-and-service-delivery.md)
(don't invent rules) and regenerate the HTML per `product/CLAUDE.md`.
- **Contract (consume):** types/services derive from
[`dev/contracts/domains/bookings-evv.md`](../../contracts/domains/bookings-evv.md) (b9). Any missing or
ambiguous shape — care-instructions decrypt response, `check_in_address_match` representation,
`payout_eligible_at` exposure, the care-details write-back — is **appended** to
`dev/shared-working-context/frontend/requests/for-backend.md` (you never edit backend files), and mocked
behind `services/bookings` meanwhile.
- **Handoff & report:** append your phase summary to
`dev/shared-working-context/frontend/STATUS.md`; write
`dev/shared-working-context/reports/frontend-phase-8-report.md` — what was built (booking detail/timeline,
sessions, nurse EVV, gated care), **what is now testable and exactly how** (the §7 steps), what is mocked
(`ILocationProvider`, any mocked `services/bookings` calls) and how to make it real, the contract consumed
+ gaps filed, follow-ups for f9/f13. Update
`dev/shared-working-context/reports/mocks-registry.md` for the `ILocationProvider` seam and any mocked
endpoint.
- **Memory:** save a `project` memory note for the non-obvious decisions — the two-stage care-instructions
**UI gate** (don't-even-fetch), the **advisory-not-blocking** EVV mismatch handling, the
`ILocationProvider` GPS seam, and that the status timeline is strictly server-truth — with a one-line
pointer added to `MEMORY.md`.