frontend phase 13

This commit is contained in:
hamid
2026-07-10 16:58:15 +03:30
parent 6186f54294
commit 85488bc25b
57 changed files with 3283 additions and 105 deletions
@@ -12,6 +12,32 @@ for awareness.
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
-->
## 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)
@@ -413,3 +413,46 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
`net_amount = gross_earnings clawback_applied`; a payout's booking-link `payout_amount_irr` sum = its
`gross_earnings_irr`; the nurse amount is **payment-method-invariant** (BNPL provider commission never deducted).
- **Status:** open
## REQ-026 — Review eligibility + my-review-for-booking reads (+ masked author confirmation) — filed by frontend-phase-13-b14 — 2026-07-10
- **Need:** Three customer-facing additions the leave-a-review flow renders that b14 does not serve:
1. **Review eligibility** — proposed `GET api/v1/bookings/{bookingId}/review_eligibility` (owner-scoped) →
`{ canReview: boolean, reason?: 'not_completed'|'already_reviewed'|'not_owner'|'not_found' }`. Lets the
CTA/form gate on "this booking is completed/closed AND not already reviewed" without probing the 1:1 `409`.
2. **My review for a booking** — proposed `GET api/v1/bookings/{bookingId}/my_review` (owner-scoped) →
`{ moderationStatus: 'pending_moderation'|'published'|'hidden'|'rejected'|'none', rating?, body?, tagCodes[], createdAt? }`.
Lets the CTA show the **persistent "under review" state across sessions** (a returning customer must never
see a second form) — today only the just-submitted result is known client-side.
3. **Masked author confirmation.** `ReviewListItemDto` (the public list) carries **no customer name**. The
client renders a generic masked author («کاربر بالین‌یار») on the profile reviews tab. Please confirm this
omission is intentional (privacy), or add a **server-masked** display name field if a masked name is wanted.
- **Why:** b14 serves the submit (`POST bookings/{id}/review`), the public `GET nurses/{id}/reviews`, and the tag
rollup — but there is no eligibility read and no my-review read, and moderation (`pending_moderation → published`)
is admin-only (f15). So the `services/reviews` domain is **mock-primary** (`USE_REVIEWS_MOCK = true`): the mock
reads the shared f8 bookings store for completed-booking eligibility, tracks the submission for the under-review
state, and seeds a published list per nurse. The real `reviewsClientApi` maps `getNurseReviews`/`createReview`
1:1 and targets the two proposed slugs for the gaps — one config flip when they land.
- **Status:** open
## REQ-027 — Family-owned care record (medications/routine/tasks) + record access + structured task results — filed by frontend-phase-13-b14 — 2026-07-10
- **Need:** The b14 `care_records` GET/POST serve the **nurse-authored visit-note history** (سوابق) — that half
is **real and consumed**. But the E2 record viewer's **family-owned editable record** and the nurse **task
checklist** have **no backend at all** (neither the b14 contract nor `data-model/10-reviews-and-records.md`
models them). Proposed:
1. **Family-owned record**`GET/PUT api/v1/patients/{patientId}/care_record` (owner-scoped for read;
owning-customer for write) → `{ patientId, medications: [{ id, name, dosage?, frequency, timingNote? }],
routine: [{ id, label, timeOfDay?, note? }], tasks: [{ id, label, done }] }`. **Patient-scoped, not
booking-scoped**; the **customer** owns/edits it; it persists across nurse changes. (New entity/table.)
2. **Record access** — proposed `GET api/v1/patients/{patientId}/record_access`
`{ canView, canEdit, canAppendNote, deniedReason? }` **or** confirm the client should derive access purely
from the **`403` on the history read** (the two-stage clinical-disclosure rule). Today the mock returns a
`403` for a foreign patient id so the non-leaking access-denied card is demoable.
3. **Structured task results on a visit note**`WriteCareRecordBody` is only `{ bookingId?, body }`. The
nurse's ticked checklist is currently **folded into `body`** as a leading summary line. A structured
`taskResults: [{ label, done }]` field would preserve it (and let the history render the checklist as chips).
- **Why:** E2 (داروها/روتین/سوابق/وظایف) + the nurse E3 task-checklist are the continuity-of-care surface. The
visit-note history/append are real (b14) and mapped 1:1 in `patientRecordsClientApi`; the family-record + access
methods target the proposed slugs (REQ-027) and the domain is **mock-primary** (`USE_PATIENT_RECORDS_MOCK = true`)
until they land. **Note:** the wireframe's four-tab E2 record is not in the data model — please confirm whether
the family-owned record is a real MVP entity or a future addition (the client treats it as forward-looking).
- **Status:** open
@@ -0,0 +1,156 @@
# Frontend Phase 13 (b14) — Reviews & Patient Care Records — report
**Track:** frontend · **Consumes:** [`reviews-records.md`](../../contracts/domains/reviews-records.md) (b14) +
`openapi/swagger.v1.json` · **Status:** complete, gate green.
The last feature-domain frontend phase: the moderated **review** loop and the **patient care-record** viewer/
authoring. Two new `services/{domain}` domains, four screens (+ a tab added to C3 + a CTA on the customer
booking detail), four new shared composites.
---
## 1. What was built
### Services
- **`services/reviews`** (`types`/`keys`/`constants`/`apis{index,clientApi,mockApi}`/`hooks`/`index`) — the
moderated-review domain. Hooks: `useNurseReviews` (infinite, published-only aggregate + list),
`useReviewEligibility(bookingId)`, `useMyReviewForBooking(bookingId)`, `useCreateReview` (invalidates
eligibility + my-review, **never** the public list/aggregate).
- **`services/patientRecords`** (same shape) — the continuity-of-care domain, **patient-scoped**. Hooks:
`usePatientCareRecord` (family record), `useRecordAccess` (gates before any clinical fetch),
`usePatientHistory` (paged visit-note history), `useUpdateCareRecord` (**customer-only** edit → `setQueryData`),
`useCreateVisitNote` (**nurse-only** append → invalidates history).
- **`services/patients`** — added `usePatient(id)` (E2 identity header; the seam already had `get(id)`).
### Screens & flows
- **Leave-a-review**`/bookings/[id]/review` (`RatingInput` + body + `ReviewTagSelector`), gated on
completed/closed + server `can_review` + 1:1; on submit → persistent **"under review"** state (never public
here); already-reviewed shows the review's moderation state, never a second form. Entry: **`LeaveReviewCta`**
on the customer booking detail (page-only sibling, reads the cached booking + my-review).
- **Nurse-profile reviews tab (C3)** — added a «خدمات» / «نظرات» tab strip to the existing profile; the reviews
panel renders the **published-only** aggregate (rating + count) + an infinite list. The old single-review
snippet is subsumed.
- **E2 patient record viewer**`/patients/[id]/record`: reused `PatientHeader` + ownership banner + four tabs
(داروها/روتین/سوابق/وظایف). The customer edits medications/routine/tasks; **سوابق** is the read-only nurse
visit-note history; a first-class, non-leaking **access-denied** card gates before any clinical fetch. Tapping
a `PatientCard` opens it.
- **Nurse visit-note authoring (E3)**`NurseVisitNotesPanel` (co-located at `nurse/visits/[id]/`), mounted
**below** the f8 EVV banner: today's task checklist + a free-text note composer + the read-only continuity
history. **Append-only** — it exposes no record editing and never wires `useUpdateCareRecord`.
- **Longitudinal history** — patient-scoped, paged (Prev/Next when >1 page), read-only, newest-first, rendered
via the shared `VisitNoteCard` in both the E2 سوابق tab and the nurse continuity view; persists across nurse
changes (the mock seeds a multi-nurse default).
### Shared composites (each with a co-located `*.test.tsx`)
- **`RatingInput`** — 15 star input/display (custom, on the registered `star` `AppIcon`; filled
`var(--bal-warning)` / empty `var(--bal-divider)`; interactive = radiogroup, readOnly = static).
- **`ReviewTagSelector`** — multi-select review-tag chip group (i18n-free; caller passes `labelFor(code)`).
- **`VisitNoteCard`** — one read-only visit note (nurse name + Shamsi date + body + done/not-done task chips).
- **`PatientHeader`** — extracted from `PatientCard` so E1 + E2 share the identity header; `PatientCard` now
composes it + gained an optional `onOpen` tap handler.
- New icons registered: `notes`, `routine`, `tasks`, `history`, `family`.
- i18n: new top-level `reviews` + `records` namespaces (both locales, in sync) + `search.tab_{services,reviews}`
+ `patients.open_record`.
---
## 2. What is testable and exactly how (per phase §7)
Run `npm run dev` (with `NEXT_PUBLIC_API_URL` set; the b14 backend reachable or the seam mocks active — both
default on).
1. **Leave a review on a completed booking → pending → appears after moderation.** As a customer, open the
completed **booking 5005** (seeded in the f8 mock) → its detail shows **«ثبت نظر»**. Open it, give 4 stars +
body + a tag chip, submit → the screen shows **«در حال بررسی / under review»** and the CTA no longer offers a
second review. The review does **not** appear on the nurse's profile. To watch it publish (the f15 admin path
is deferred), in the browser console call the reviews mock's dev helper
`__mockPublishSubmittedReview(5005)` (exported from `services/reviews/apis/mockApi.ts`) → it appears on the
**nurse 1** profile reviews tab and the aggregate updates on the next fetch. A **cancelled** booking (5004)
shows no review CTA (not completed).
2. **View a patient record with tabs + ownership banner.** Patients tab → tap a patient → E2 shows the four
tabs, medication cards under داروها, the **«این پرونده متعلق به خانواده است …»** banner, and the customer can
edit a medication/routine/task (**ویرایش** → change → **ذخیره**) and see it persist (cache updates via
`setQueryData`, no reload). Navigating to **`/patients/8888/record`** (the `MOCK_FOREIGN_PATIENT_ID`) shows
the **access-denied** card, not a crash.
3. **A nurse appends a visit note (cannot edit the record).** As the nurse, open a visit (`/nurse/visits/5005`)
→ below the EVV banner, tick the task checklist, write a note, **«ثبت یادداشت»** → the note saves and shows in
the history. There is **no** medication/routine/task edit control anywhere in the nurse view.
4. **History persists across nurse changes.** The سوابق tab (customer) and the nurse continuity view show the
patient-scoped, newest-first timeline — including seeded notes from a **different** nurse (سارا محمدی) —
paginated.
5. **Gate checks:** `npm run check` green; `npm run test:ci` green (all suites); the reviews tab/list + record
edit show query caching + invalidation in React Query Devtools (no needless refetch).
---
## 3. What is mocked client-side + how it swaps
Both domains are **mock-primary** behind their `services/{domain}` seams (`USE_REVIEWS_MOCK`,
`USE_PATIENT_RECORDS_MOCK`, default `true`). See [`mocks-registry.md`](./mocks-registry.md) for the full rows.
- **Reviews:** `reviewsClientApi.getNurseReviews`/`createReview` map the live b14 routes **1:1**; the mock adds
what b14 doesn't serve — review-eligibility + my-review-for-booking (**REQ-026**) — and stands in for the
admin-only moderation (f15) via `__mockPublishSubmittedReview`. Swap = flip `USE_REVIEWS_MOCK` once REQ-026 lands.
- **Patient records:** `patientRecordsClientApi.getPatientHistory`/`createVisitNote` map the **real** b14
`care_records` GET/POST 1:1; the mock adds the family-owned record + access check (**REQ-027**, no backend
entity exists). Swap = flip `USE_PATIENT_RECORDS_MOCK` once REQ-027 lands (only the family-record/access
methods change).
- **f8 bookings mock (non-seam):** added **booking 5005** (`completed`) so a review is demoable + the cross-mock
read helper `mockGetBookingForReview` (one-way edge into bookings, no cycle).
---
## 4. Contracts consumed / requests filed
- **Consumed (real, mapped 1:1):** `POST bookings/{id}/review`, `GET nurses/{id}/reviews`,
`GET`/`POST patients/{id}/care_records`.
- **Filed** to [`for-backend.md`](../frontend/requests/for-backend.md):
- **REQ-026** — review-eligibility read + my-review-for-booking read + confirm the masked-author omission on
`ReviewListItemDto`.
- **REQ-027** — the family-owned `care_record` (medications/routine/tasks) GET/PUT + a `record_access` read
(or derive from the 403) + structured `taskResults` on a visit note. Flags that the wireframe's four-tab E2
record has **no data-model entity** — needs a product decision.
---
## 5. Follow-ups for later phases
- **⚠ Discovered pre-existing infra defect (repo-wide, NOT in the f13 diff): the ESLint unused-vars gate is a
no-op.** `client/eslint.config.mjs` tries to raise `@typescript-eslint/no-unused-vars` to `error` by
`.map()`-patching `eslint-config-next`'s default export — but that export never carries the rule (it lives in
the `eslint-config-next/typescript` subpath, which the config doesn't spread), so the patch matches nothing
and the rule is **never enabled** (verified via `eslint --print-config` — zero active `@typescript-eslint`
rules). `tsconfig.json` also lacks `noUnusedLocals`. Net effect: **unused imports/vars pass `npm run check`
silently**, contradicting `client/CLAUDE.md` golden rule #11 and the config's own comment. This is why the
adversarial review (not the gate) caught three dead imports in this diff — now removed; **the f13 diff is
verified 0 unused via `tsc --noEmit --noUnusedLocals --noUnusedParameters`.** The fix (spread
`eslint-config-next/typescript`, or re-register the rule in an explicit `**/*.{ts,tsx}` override, + fix the
false CLAUDE.md/config comments, + optionally add `noUnusedLocals`) is **deliberately deferred** here: it
surfaces ~6 pre-existing violations in earlier-phase files, so it wants a focused repo-wide cleanup + a
team decision, not a f13-scoped change. **Recommend a dedicated infra task.**
- **Review moderation UI** (admin approve/hide/reject queue) is **DEFERRED → f15** (`frontend-phase-15-b15`).
The client is publish-only; the dev helper stands in until then.
- **Tag-aggregation dashboards** ("% punctual") are deferred (the `GET nurses/{id}/review_tags` endpoint exists
and could back them). This phase renders per-review tag chips only.
- The **in-app "raise a concern" flag + emergency banner**`frontend-phase-14-b15`.
- Once REQ-026/027 land, flip the two mock flags; no hook/component change.
---
## 6. Gate
`npm run check` green (tsc + eslint). `npm run test:ci` green (257 tests, 58 suites; +17 new across
`RatingInput`/`ReviewTagSelector`/`VisitNoteCard`/`PatientHeader`/`PatientCard`). `npm run build` green with
`NEXT_PUBLIC_API_URL` set (all new routes compile; the env-required build failure without it is pre-existing and
unrelated). The f13 diff is verified **0 unused** via `tsc --noEmit --noUnusedLocals --noUnusedParameters`.
A **6-dimension adversarial review** (contract-fidelity, caching, security/access, i18n/RTL/tokens,
React-correctness, flow-integrity) ran with per-finding adversarial verification. Outcome:
- **Confirmed & fixed:** 3 dead imports (`Box` + `RECORD_HISTORY_PAGE_SIZE` in the E2 page,
`mockListBookingsForReview` in the reviews mock — the latter's orphaned bookings-mock export also removed);
the now-inaccurate history-only invalidation doc comment; the orphaned `search` i18n keys my C3 refactor left;
an unreachable task-checklist skeleton branch.
- **Confirmed but deferred:** the pre-existing ESLint-gate no-op (§5, repo-wide infra).
- **Verified false-positive (no change):** the `409`-for-not-completed (the contract specifies no status for
that case, only for the 1:1 `409`); the security/access dimension raised **nothing** (published-only,
append-only, and the non-leaking access gate all hold); the caching dimension raised **nothing**.
@@ -77,3 +77,6 @@ the frontend can build before the backend phase merges, and swap to the real HTT
| `BnplApi` | `client/src/services/bnpl/apis/mockApi.ts` | **The f11 BNPL installment checkout (D1D5)** b12 doesn't serve client-side (b12 is order-centric — eligibility/initiate/status/webhook — and **explicitly does not model the repayment schedule**; no provider/plan options, no wallet installment status → REQ-022/023/024). Reads the frozen request gross from the shared **f7 store** and plays the provider: `getBnplOptions` builds the provider set as **data** (دیجی‌پی 3/6/12 · اسنپ‌پی ۴ · اقساط بالین‌یار; per-plan monthly/down-payment/total via **integer parts-per-10000 BigInt math**, never a hardcoded fee in the UI); `checkEligibility` returns `eligible` unless the national-id last digit is `0` (→`not_eligible`) or the order exceeds `MOCK_CREDIT_CEILING_IRR` (→`ceiling_exceeded`) so both declined paths demo; `getBnplSchedule` serves the down-payment + N-installment rows (last absorbs the remainder → rows sum to total); `issueBnplToken` enforces b12 idempotency (same key → same token; repeat after settle / lapsed window → **`409`**) + a `redirectUrl` into the local provider-handoff harness; `acceptBnplSchedule` on success is the **settle stand-in and reuses the f9 conversion bridge** — flips the request `converted` (`mockMarkBookingRequestConverted`), inserts a **confirmed** booking (`mockInsertConvertedBooking`; a settled BNPL order = a card payment net-of-fee, payout invariant to method), and **seeds a provider-reported Wallet plan**; `getWalletInstallments` serves D5 (seeded active دیجی‌پی ۶-ماهه with paid/due-soon/upcoming rows + each settled checkout's plan). Money = served IRR digit-strings end-to-end (components only format) | `USE_BNPL_MOCK` (`services/bnpl/constants.ts`, default `true`) | Deliver **REQ-022** (options + schedule — real `bnplClientApi` targets `checkout_bnpl/options/{id}` + `checkout_bnpl/schedule/{id}`), **REQ-023** (eligibility accepts the D3 national-id/mobile/consent), **REQ-024** (`checkout_bnpl/wallet_installments` provider-reported status + a customer `bookingId` on the settled order), and make the upstream `bookingRequests` flow real, then set flag `false``checkEligibility`/`issueBnplToken`(`Idempotency-Key`)/`getBnplOrder` already map the live b12 routes 1:1; the settle-on-return reads the order (the real settle is the provider webhook). No hook/component change | 🟡 |
| BNPL provider-handoff harness (test harness) | `client/src/app/[locale]/(private-routes)/(customer)/bookings/checkout/bnpl/gateway/page.tsx` | **Not a product feature** — a dev stand-in for the provider's hosted BNPL page so the initiate → redirect → return round-trip is exercisable without a provider: the mock `redirectUrl` points here, and its pay/cancel buttons drive both branches of the return surface (`?outcome=success\|failure`). Clearly labelled «در حال انتقال به ارائه‌دهنده», dashed border | _none — only reachable via the mock's `redirectUrl`_ | On the real path b12's `redirectUrl` is the provider's **absolute** URL (the wizard does a full `window.location.assign` for `http(s)`), so this page is never linked; delete it when `USE_BNPL_MOCK` retires. The provider's return deep-link into `/bookings/checkout/bnpl/return` is backend/provider config | 🟡 |
| `PayoutsApi` | `client/src/services/payouts/apis/mockApi.ts` | **The f12 nurse earnings surface** b13 doesn't serve read-side for a nurse (b13's only nurse route is `GET nurse_payouts/history`; the four-bucket **earnings summary**, the per-booking **earnings list + money-state**, and a **nurse-readable payout detail** with batch context + booking links are gaps → **REQ-025**). Self-contained, money-correct fixtures exercising **every** UI state: all four earnings states (`pending`/`eligible`/`paid`/`clawback_applied`; booking ids 50015004 align with the f8 bookings-store seeds so "view booking" deep-links land), all four `PayoutStatus` values in history (`pending`/`submitted`/`paid`/`failed`, incl. a `failed` payout with `failureReason: 'invalid_sheba'` for the read-only failure banner), payout **details that reconcile** (`gross clawback = net = amount`, Σ booking-link amounts = `grossEarnings`), and a **signed net balance** computed with BigInt via a `MOCK_SCENARIO` toggle (`standard` = positive; **`clawback_heavy` = negative "owed back"** for phase §7 step 3). Timestamps are relative to `now` so the pending dispute-window countdown always ticks; money stays IRR digit-strings end-to-end (components only format). `getNurseEarnings` filters by `state` + paginates | `USE_PAYOUTS_MOCK` (`services/payouts/constants.ts`, default `true`) + `MOCK_SCENARIO` in `constants.ts` | Deliver **REQ-025** (earnings_balance + earnings list + nurse `nurse_payouts/{id}` detail + `failureReason` on the history DTO), then set flag `false``payoutsClientApi` already maps the live `GET nurse_payouts/history` 1:1 and targets the proposed slugs for the other three. No hook/component change | 🟡 |
| `ReviewsApi` | `client/src/services/reviews/apis/mockApi.ts` | **The f13 moderated-review trust loop.** b14 serves the review **submit** (`POST bookings/{id}/review`), the public **nurse reviews** page (`GET nurses/{id}/reviews`), and the tag rollup — those are mapped 1:1 in `reviewsClientApi`. But there is **no review-eligibility read** and **no my-review-for-booking read** (**REQ-026**), and the whole moderation transition (`pending_moderation → published`) is **admin-only (f15)**. The mock reads a booking from the shared **f8 bookings store** (`mockGetBookingForReview`) to gate eligibility on a **completed/closed** booking (aligns with the new completed seed 5005 / nurse 1 / patient 905), tracks the customer's submission as `pending_moderation` so eligibility flips `already_reviewed` + `getMyReviewForBooking` returns the persistent "under review" state, and seeds a **published list per nurse** (nurse 1 has 7 → the profile tab paginates; nurses 5/6 empty → empty state). The aggregate is **recomputed from the published list** (never a stored sum). A submitted review **never** enters any public list. Dev-only `__mockPublishSubmittedReview(bookingId)` stands in for the deferred (f15) admin queue so a human can watch a review appear on the profile. Money-free | `USE_REVIEWS_MOCK` (`services/reviews/constants.ts`, default `true`) | Deliver **REQ-026** (`review_eligibility` + `my_review` reads; confirm masked-author omission), then set flag `false``reviewsClientApi.getNurseReviews`/`createReview` already map the live b14 routes 1:1 and target the two proposed slugs for the gaps. Moderation UI itself is **f15** (admin). No hook/component change | 🟡 |
| `PatientRecordsApi` | `client/src/services/patientRecords/apis/mockApi.ts` | **The f13 continuity-of-care surface.** Two very different things: (1) the **nurse-authored visit-note history** (`getPatientHistory`/`createVisitNote`) is **REAL b14** (`GET`/`POST patients/{id}/care_records`), mapped 1:1 in `patientRecordsClientApi` (the append composes the ticked task checklist into the note `body` since the wire has no structured task field); (2) the **family-owned editable record** (medications/routine/tasks — the داروها/روتین/وظایف tabs) and the **access check** have **NO backend at all** (neither the b14 contract nor `data-model/10-reviews-and-records.md` model them → **REQ-027**). The mock is **patient-scoped** and lazily seeds a coherent default per patient: a default family record (customer edits it), a **multi-nurse continuity history** (two prior notes from *different* nurses, proving the history persists across nurse changes; a nurse append prepends to the same patient's history), and a **foreign-patient access-denied** path (`MOCK_FOREIGN_PATIENT_ID = 8888``canView:false` + a `403` on every read) so the non-leaking access-denied card is demoable. Clinical text is fixture data (never logged) | `USE_PATIENT_RECORDS_MOCK` (`services/patientRecords/constants.ts`, default `true`) | Deliver **REQ-027** (family-owned `care_record` GET/PUT + `record_access` + structured `taskResults`), then set flag `false` — the history/append methods already map the real b14 routes; only the family-record/access methods flip. Confirm whether the family-owned record is a real MVP entity | 🟡 |
| f8 bookings mock — completed-booking seed 5005 + f13 cross-mock reads | `client/src/services/bookings/apis/mockApi.ts` | **Non-seam additions (mirrors the f10 refunds precedent).** The f8 seeds had **no `completed` booking** (only `confirmed`/`in_progress`/`cancelled`), so f13's review flow needs one: added **booking 5005** (`status: 'completed'`, nurse 1, patient 905, one completed EVV session) so the customer can open a completed booking and leave a review. Also added a **cross-mock read helper**`mockGetBookingForReview(id)` (single booking, clone) — imported by the reviews mock to gate eligibility and read the patient/nurse snapshot for a submission (the `listBookings` seam row omits `patientId`/`nurseId`). One-way edge INTO bookings (the bookings mock never imports f13), so no cycle | — (part of `USE_BOOKINGS_MOCK`) | When the bookings flow goes real (b9/b10 conversion live), 5005 stops being a static seed and the cross-mock helpers retire with the reviews/records mocks | 🟡 |