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
+339
View File
@@ -0,0 +1,339 @@
# Frontend Phase 5 — Nurse verification flow (mocked vendors)
> **Mission:** build the trust engine's front end — the staged, platform-owned verification flow a nurse
> walks through before any of their services can go live. A nurse lands on a **status checklist** (B3),
> submits **identity** (B4: national-ID + card image + liveness selfie), submits **professional
> credentials** (B5: نظام پرستاری number + license + education + specialties), and waits on the
> **under-review** screen (B6) until an admin decides. Documents upload to the object-storage-backed
> endpoint with type/size validation and progress; each step shows its own status (and its rejection
> reason); and a **trust badge** (verified / unverified / expired) renders on the nurse profile. Verified
> trust is the entire brand — a nurse is **not bookable and cannot publish until verified**, and the UI
> must say so honestly and never advertise a check the platform doesn't actually perform.
>
> **Track:** frontend · **Depends on:** [`frontend-phase-4-b5.md`](./frontend-phase-4-b5.md) (catalog browse + nurse service builder) and the **b6** verification contract · **Unlocks:** a bookable verified nurse + the search **trust badge** consumed in [`frontend-phase-6-b7.md`](./frontend-phase-6-b7.md)
> **Before you start, read [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md).** It is not optional.
---
## 1. Context — where this sits
This is the fifth feature slice of the customer/nurse front end and the **nurse side's gating step**.
By f4 a nurse can build service variants, but those variants are **inert** — they cannot surface in
search or be booked until `nurse_profiles.is_verified` is true. This phase builds the screens that flip
that switch: the data-driven verification pipeline (six step types, all vendor calls **mocked** server-side
behind DI seams in b6) rendered as a nurse-facing checklist, the two submission forms, the waiting state,
the document uploader, and the public trust badge. When this lands, a nurse can go from "registered" to
"verified and publishable", which is the prerequisite for everything downstream — search (f6), booking
(f7), and ultimately payout (f12, gated on the bank-account verification step).
**What already exists (do not rebuild) — built by prior phases:**
- **f0 foundations** ([`frontend-phase-0.md`](./frontend-phase-0.md)): the three actor app shells +
route groups, the **nurse shell** ("نمای پرستار"), the `services/{domain}` + TanStack Query caching
pattern (`keys.ts` factory, `apis/clientApi.ts`, one-hook-per-file), the contracts→`types.ts` pattern,
the money/format utils, and the **shared composite components** — most importantly the **stepper/
progress header** and the **status chip** (verified/pending/…). **Reuse both here; do not re-implement
a stepper or a status chip.** The `verification` i18n namespace was reserved in f0 — fill it.
- **f1-b2 auth** ([`frontend-phase-1-b2.md`](./frontend-phase-1-b2.md)): phone-OTP login, the role router,
roles in `AuthContext`. The nurse arrives here already authenticated with the `nurse` role. **Step 1 of
the checklist (شماره موبایل — mobile verified) is already satisfied at login** — render it as `passed`,
don't ask for it again.
- **f2-b3 onboarding** ([`frontend-phase-2-b3.md`](./frontend-phase-2-b3.md)): the nurse profile bootstrap
(`CreateNurseProfileCommand` → an unverified `nurse_profiles` row, `is_verified=false`) and the nurse
**bank-account settings** screen (IBAN entry → ownership inquiry). The verification pipeline's
`bank_account_verification` step couples to that bank account — **link to the existing bank-account
screen for that step; do not rebuild the IBAN form here.**
- **f3-b4 addresses/geo** ([`frontend-phase-3-b4.md`](./frontend-phase-3-b4.md)): the nurse coverage-area
editor and the cascading geo dropdowns (not used here, but the nurse shell nav points to them).
- **f4-b5 catalog** ([`frontend-phase-4-b5.md`](./frontend-phase-4-b5.md)): the nurse "add a service"
builder (B7) and `services/catalog`. **The "انتشار پروفایل / go live" action that f4 stubs must be
gated on verification by this phase** — wire the blocked-until-verified state into the publish CTA.
> **Honesty constraint (load-bearing, from the product doc and GTM notes):** vetting is platform-owned and
> performed at the authoritative source. **Never word the UI to advertise a check that isn't performed.**
> The MoH/INO license and criminal-record steps are *manual admin review of an uploaded document* at
> launch — copy must say "در حال بررسی" (under review), not "تاییدشده توسط نظام پرستاری" until an admin
> actually passes it. The civil-registry/Shahkar/liveness checks are real (vendor-mocked) — those may say
> "استعلام خودکار".
## 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, the gate, the contract/handoff lanes, the mock-then-swap rule (§6).
- [`../../../client/CLAUDE.md`](../../../client/CLAUDE.md) — the engineering contract (RSC/client boundary,
layouts, the `services/{domain}` shape, i18n, theme, cookies, the fetch services). Non-negotiable.
- **Invoke the `frontend-designer` skill** before any visual work — it is the design/brand contract
(palette, tokens, typography, the `App*` library, layout shells, the hard UI rules). Every screen,
chip, uploader, and badge in this phase goes through it. The wireframe's status legend is **green =
automatic/verified, amber = pending, grey = manual/next, terracotta = financial** — encode those as
token-driven chip variants.
- **Product — the source of truth for the rules:**
- [`../../../product/business/02-nurse-verification.md`](../../../product/business/02-nurse-verification.md) —
the six steps, what each verifies, why it's manual vs automated, the structured-credential-registry
rationale, continuous re-verification, and **the "never advertise a check you don't perform" rule**.
- [`../../../product/wireframes/index.html`](../../../product/wireframes/index.html) — **Section B,
screens B3B6** are the visual baseline you implement: B3 status meter "۲ از ۵" + stepped checklist
with status badges; B4 identity (کد ملی field, upload national-ID card, liveness selfie, "استعلام
خودکار از ثبت احوال" note); B5 professional credentials (شماره نظام پرستاری, license upload, education
cert shown uploaded ✓, specialty chips سالمندان/ICU/+افزودن); B6 "در حال بررسی" (2448h, mini-checklist).
Also note B7's "انتشار پروفایل" — the publish gate this phase enforces, and C2/C3's "✓ تاییدشده" badge
this phase's trust-badge component feeds.
- **The contract you consume (the authoritative server shapes):**
- [`../../contracts/domains/verification.md`](../../contracts/domains/verification.md) — written by
**backend-phase-6**. The exact request/response shapes, routes, status enums, and the per-step status
codes. **Do not guess shapes** — derive `types.ts` from this doc + the published `swagger.json`. If a
shape you need is missing, append a request to
[`../../shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)
and mock behind the `services/verification` seam meanwhile (operating-rules §6).
- [`../../contracts/conventions/api-conventions.md`](../../contracts/conventions/api-conventions.md) and
[`money-and-types.md`](../../contracts/conventions/money-and-types.md) — the envelope (`OperationResult`
`ApiResult`, already unwrapped by `clientFetch`), `snake_case` routes/properties, pagination, **enums
as stable string codes** (mirror them as string-literal unions; labels are i18n keys, never derived
from the code), UTC timestamps with **Shamsi display client-side** (credential issue/expiry dates).
- **Code to mirror:**
- The `services/catalog` and `services/onboarding` domains from f4/f2 — the exact `types.ts`/`keys.ts`/
`apis/clientApi.ts`/`hooks/use*.ts`/`index.ts` layout your `services/verification` copies.
- The shared **stepper/progress header** and **status chip** from f0 (`src/components/…`) — extend, don't
fork. The nurse-profile bank-account screen from f2 — you deep-link into it for the bank step.
- The existing document/image handling, if any, in `App*` (`AppImage`); the `clientFetch` multipart path.
## 3. Scope — build this
A new **`services/verification`** domain, the **nurse verification route subtree** under the nurse shell,
a reusable **document-upload** component, a **trust-badge** component, and the publish-gate wiring. Build
RTL-first, both locales, query-cached, minimal re-renders.
### 3.1 `services/verification` domain (the data layer)
Copy the f0/f4 service shape exactly. Types come from
[`../../contracts/domains/verification.md`](../../contracts/domains/verification.md) — do not invent.
- **`types.ts`** — string-literal unions mirroring the contract enums: the **aggregate status**
(`not_started` | `pending` | `in_review` | `approved` | `rejected` | `suspended`), the **per-step
status** (`not_started` | `pending` | `in_review` | `passed` | `rejected`), the **step `code`s**
(`identity_kyc` · `shahkar_match` · `moh_competency_license` · `ino_membership` · `criminal_record` ·
`bank_account_verification`), `verification_method` (`manual` | `portal` | `api`), and the
badge state (`verified` | `unverified` | `expired`). Plus the DTOs the contract returns:
`VerificationStatus` (aggregate + ordered `steps[]` each with `code`, `status`, `is_automated`,
`rejection_reason?`, `expires_at?`), `VerificationStep`, `VerificationDocument` (storage key, file name,
uploaded-at — **metadata only, never bytes**), `NurseCredential` (type, masked number, holder name,
issuing authority, `issued_at`/`expires_at`).
- **`keys.ts`** — a query-key factory: `verificationKeys.status()`, `.documents(stepCode)`,
`.badge(nurseId)`. Deliberate `staleTime` (status is moderately fresh — e.g. 30s — because submitting a
step changes it; the badge is longer-lived).
- **`apis/clientApi.ts`** wrapping `clientFetch` — one function per contract endpoint (see §3.2). The
document upload uses the multipart path against the `IObjectStorage`-backed endpoint.
- **`hooks/` — one hook per file:**
`useVerificationStatus` (query), `useStartVerification`, `useSubmitIdentity`, `useSubmitCredentials`
(or per-credential submit if the contract splits MoH/INO/criminal), `useUploadVerificationDocument`
(mutation with progress), `useNurseTrustBadge` (query). **Every mutation invalidates
`verificationKeys.status()`** (and the badge where relevant) so the checklist re-renders from cache
without a manual refetch. Don't toast 401/403/5xx (the fetch layer does) — only domain 4xx (e.g.
"ownership mismatch", "shared-SIM", "national-ID format").
### 3.2 Endpoints consumed (per the b6 contract — confirm exact routes/shapes there)
Wire each via `clientApi`; the names below are the expected commands/queries from the digest — bind to
whatever the contract publishes:
- `GET .../nurse/verification``GetVerificationStatusQuery` — the aggregate + per-step list driving B3/B6.
- `POST .../nurse/verification/start``StartNurseVerificationCommand` — seeds the steps (call from the
B3 "start / continue" CTA when status is `not_started`).
- `POST .../nurse/verification/identity``RunIdentityKycCommand` — national-ID + card image + liveness
selfie; drives B4. (Server also runs `shahkar_match` off the bound national-ID — surface both steps.)
- `POST .../nurse/verification/moh-license` (+ `.../ino`, `.../criminal-record` if split) →
the credential-submit commands behind B5.
- `POST .../nurse/verification/document` (or a presigned-URL flow) → the `IObjectStorage`-backed upload
used by every document step.
- The **bank step** (`bank_account_verification`) is submitted on the **existing f2 bank-account screen**
this phase only renders its status in the checklist and **deep-links** to that screen.
- `GET .../nurse/{id}/badge` (public) → `GetVerifiedBadgeQuery` — feeds the trust badge.
### 3.3 Screens (under the nurse shell `(private-routes)` nurse subtree)
Invoke `frontend-designer` for each. RTL-first; the screens live under the nurse route group from f0.
- **B3 · وضعیت احراز هویت — status checklist** (the hub).
- Overall progress meter **"X از Y"** (X = passed required steps, Y = total required) — reuse the f0
stepper/progress header.
- An **ordered, stepped checklist**, one row per step from `steps[]`, each with a **status chip** (reuse
the f0 status chip) in the five per-step states: `not_started`/locked-next (grey "بعدی"), `pending`
(amber "در انتظار"), `in_review` (amber "در حال بررسی"), `passed` (green "تاییدشده"), `rejected`
(red "رد شد" + the reason). Render **step 1 (mobile) as `passed`** from auth state.
- A **"what's blocking go-live" summary** + a single **continue CTA** ("ادامه مرحله N") that routes to
the next actionable step. When `not_started`, the CTA calls `useStartVerification` first.
- States: loading skeleton, error, and the terminal **`approved` state** (all passed → "احراز هویت
تکمیل شد، می‌توانید پروفایل را منتشر کنید" with a link to publish).
- **B4 · تایید هویت — identity submit.**
- **کد ملی (national-ID)** field with format validation (10-digit, checksum); **upload national-ID card
image** (camera/gallery via the document uploader §3.4); **liveness selfie** capture (camera; handle
permission-denied / retry / vendor-timeout states honestly).
- The honest auto note "استعلام خودکار از ثبت احوال" (auto civil-registry query) — this check *is*
performed (vendor-mocked), so the copy is allowed.
- Submit → `useSubmitIdentity`; on success the `identity_kyc` (and server-run `shahkar_match`) steps move
to `pending`/`in_review`, the cache invalidates, and the user returns to B3. Surface the **shared-SIM**
Shahkar failure as a clear, **non-accusatory** message, and the national-ID-mismatch failure on its step.
- **B5 · مدارک حرفه‌ای — professional credentials.**
- **شماره نظام پرستاری (INO number)** field; **upload پروانه/کارت نظام پرستاری** (the MoH competency
license — the single most important credential) via the uploader; **education certificate** upload
(shown with the uploaded-✓ state when present); **specialty chips** (multi-select: سالمندان, ICU, +
افزودن — an add-your-own chip input).
- Structured fields the registry needs where the contract asks for them (license number, issuing
authority, holder name as printed, issue/expiry date — **Shamsi date picker, stored UTC**).
- Submit → `useSubmitCredentials`; the credential steps move to `in_review` (manual admin review). Copy
must reflect manual review, not an automated authority confirmation.
- **B6 · در حال بررسی — under review.**
- The waiting state: "مدارک شما در حال بررسی است" + the **2448h** expectation + a **mini-checklist**
summarizing which steps are passed vs in-review (identity verified / professional docs in review /
bank account pending). Reuses the same per-step chips as B3, condensed. CTA "مشاهده وضعیت" → B3.
- This is the post-submission resting screen; B3 is the canonical source — B6 is a focused view of the
same `VerificationStatus`, not a second fetch with different state.
- **Trust badge on the nurse profile.** Render the **verified / unverified / expired** badge (the
"✓ تاییدشده" mark) on the **nurse's own profile** (f2) and export a small `<TrustBadge status=…/>`
shared component so **search results (C2) and the public nurse profile (C3) in f6 reuse it**. Source it
from `GetVerifiedBadgeQuery`. `expired` (a required credential lapsed) shows distinctly from `unverified`.
- **Publish gate.** Wire the f4 "انتشار پروفایل / go-live" action: when the aggregate status is not
`approved`, the publish CTA is **disabled with a blocked-until-verified explanation** ("برای انتشار،
احراز هویت را کامل کنید") that links to B3. This is the front-end enforcement of "not bookable until
verified" — the server enforces it too; the UI must not let a nurse *believe* they're live when they're not.
### 3.4 Document uploader (shared composite component)
Build a reusable **`<DocumentUpload>`** at the shared level (`src/components/…`, co-located `*.test.tsx`)
composed from MUI/`App*` primitives — used by every document step (national-ID card, license, education,
criminal record):
- **Client-side validation** before upload: file **type** (jpg/png/pdf per the contract) and **size cap**;
reject with a clear field error.
- **Upload states:** idle → selecting → **uploading (progress %)** → processing/hashing → success
(thumbnail/✓ + file name) → **error (retry)**. Wire progress from the upload mutation.
- **Re-upload on reject:** when a step is `rejected`, the uploader shows the prior file's status and a
re-upload affordance.
- Returns the server's stored **document metadata** (storage key, name) — never holds or displays raw
bytes beyond a local preview.
### 3.5 i18n namespace `verification` (both locales)
Fill the f0-reserved `verification` namespace in **both** `messages/en.json` and `messages/fa.json`, in
sync: step labels and per-step status labels (one key per `code` and per status — **never** derive a label
from the enum string), the B3/B4/B5/B6 screen copy, the uploader states, the honesty-sensitive copy
(manual-review vs auto-query), the blocked-until-verified message, the trust-badge labels, and the
shared-SIM / mismatch / format error messages. `fa` is default and RTL.
### Deferred (do not build here)
- The **admin verification review queue** (pass/reject a manual step, the doc viewer, credential-entry
form) → **(DEFERRED to [`frontend-phase-15-b15.md`](./frontend-phase-15-b15.md))**, the admin backoffice.
This phase only triggers the mock approval to *observe* the state change (see §7); it does not build the
admin UI.
- **Credential renewal/expiry prompts** beyond rendering the `expired` badge state → the nurse renewal
prompt UI is **(DEFERRED)**; the server's `CredentialExpiryScannerJob` (b6) raises the alert.
- **Video interview** (B6 mentions it) — not a built step at MVP; reflect it only as a grey "next" row if
the contract returns it; otherwise omit. Do not invent an interview-scheduling flow **(DEFERRED)**.
- Customer national-ID KYC — out of scope (the product gates only nurses) **(DEFERRED)**.
## 4. Mocks & seams in this phase
This phase **introduces no new front-end seam of its own** beyond the standard `services/{domain}` data
seam — the verification *vendors* (identity KYC, Shahkar, MoH/INO license, criminal record, IBAN ownership,
object storage) are **mocked server-side** behind DI seams owned by **backend-phase-6** (`IIdentityKycProvider`,
`IShahkarVerifier`, `ICredentialVerifier`, `IBankAccountOwnershipVerifier`, `IObjectStorage`) — the front
end consumes them only through the b6 contract, exactly as if they were real. **Reuse the `services/{domain}`
mock-`clientApi` pattern from f0:** if the b6 contract or a specific shape isn't published when you start,
build a **mock `clientApi`** behind the `services/verification` seam that returns realistic, contract-shaped
`VerificationStatus` (e.g. identity → `in_review`, then flippable to `passed`), record it in your phase
report + the [mock registry](../../shared-working-context/reports/mocks-registry.md), and **append the gap
to [`for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)**. The swap to the real
endpoint must be implementation-only (same hook signatures, same `queryKey`s) — no call-site changes.
## 5. Critical rules you must not get wrong
- **A nurse is NOT bookable and cannot publish until verified.** When the aggregate status is not
`approved`, the publish/go-live CTA is disabled with the blocked-until-complete explanation, and every
service variant stays inert. This front-end gate mirrors the server's guarded `is_verified` flip — never
let the UI imply a nurse is live before verification completes.
- **Honest copy — never advertise a check that isn't performed.** Manual steps (MoH/INO license, criminal
record) say "در حال بررسی / آپلود شد"; only the genuinely-performed automated checks (identity liveness,
civil-registry, Shahkar) may say "استعلام خودکار". A step is "تاییدشده" only when its status is `passed`.
This is a product constraint, not a style preference.
- **Per-step rejected-with-reason.** A `rejected` step renders its `rejection_reason` and a clear
re-submit/re-upload path — never a dead end. The shared-SIM Shahkar failure is an explicit, handled,
**non-accusatory** state, not a generic error.
- **The data is data-driven — render `steps[]` from the contract, don't hardcode the six steps.** The
pipeline is rows server-side; the UI iterates the returned ordered list and maps each `code`/`status` to
a label + chip. A new step type appearing in the response must render without a code change.
- **Reuse the shared stepper and status chip from f0** — do not re-implement them. Concrete MUI primitives
stay MUI; the new shared pieces (`<DocumentUpload>`, `<TrustBadge>`) live at the shared level with tests.
- **Caching is a feature.** B3 and B6 read the *same* `VerificationStatus` query — one cached source, two
views; every submit/upload mutation **invalidates** the status (and badge) so the checklist updates from
cache, never an extra manual refetch. Minimise re-renders (stable refs, `select` for slices).
- **RSC/client boundary + RTL + both locales + tokens.** No `next/headers`/`next-intl/server` in client
components; design RTL-first (`fa` default); every string a key in both locale files; colours from
`tokens.css` (the chip variants map to the wireframe's green/amber/grey/red legend); MUI v9 API only.
- **Document bytes are never shown from the API** — uploads return metadata only; a local preview before
upload is fine, but the rendered "uploaded" state is driven by server metadata, not retained bytes. The
national-ID and credential numbers are sensitive — show **masked** values where the contract masks them.
- **Dates:** credential issue/expiry are UTC on the wire, **Shamsi for display** (use the f0 date util).
## 6. Definition of Done
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
- [ ] `services/verification` exists with the f0/f4 shape; types derived from
[`verification.md`](../../contracts/domains/verification.md) (not guessed); mutations invalidate the
status/badge query; one hook per file; no raw `fetch()`.
- [ ] B3 (status checklist), B4 (identity), B5 (credentials), B6 (under review) render under the nurse
shell, RTL, both locales in sync, reusing the f0 stepper + status chip.
- [ ] `<DocumentUpload>` and `<TrustBadge>` are shared components with co-located `*.test.tsx`; uploader
enforces type/size, shows progress, and supports re-upload on reject.
- [ ] The publish/go-live CTA from f4 is gated: disabled + blocked-until-verified message until `approved`.
- [ ] Honesty copy verified: manual steps never claim an automated authority check; a step shows
"تاییدشده" only when `passed`.
- [ ] Rejected steps show their reason and a re-submit path; shared-SIM is a clear non-accusatory state.
- [ ] `npm run check` green; `npm run test:ci` green (shared components added/touched); `en.json`/`fa.json`
in sync.
- [ ] `client/CLAUDE.md` *Project Structure* updated for the new route subtree + `services/verification`
+ the new shared components.
## 7. How to test (what a human can verify after this phase)
Run `npm run dev` (and the b6 server, or the mock `clientApi` if b6 isn't merged). Sign in as a `nurse`.
- **Checklist hub:** open the nurse verification screen → **B3** shows the "X از Y" meter, step 1 (mobile)
already `passed`/green, the rest `not_started`. The continue CTA routes to the next step (calling
`start` first when `not_started`).
- **Submit identity:** **B4** → enter a valid کد ملی, upload a national-ID card image (watch the
uploader's progress → success), capture a liveness selfie, submit → the `identity_kyc` and
`shahkar_match` steps move to **`pending`/`in_review`** on B3 **without a manual refresh** (cache
invalidation). Try an invalid national-ID → field error; trigger the mock shared-SIM number → a clear
non-accusatory Shahkar message.
- **Submit credentials:** **B5** → enter the نظام پرستاری number, upload the license + education cert, add
specialty chips, submit → the credential steps move to **`in_review`**; the UI lands on **B6** showing
"در حال بررسی", the 2448h note, and the mini-checklist.
- **Approval flips verified:** trigger the b6 mock admin approval (the mock provider/test endpoint, or set
the mock `clientApi` to return `approved`) → re-open B3: aggregate is **`approved`**, all required steps
`passed`, the **trust badge** shows **verified** on the nurse profile, and the **publish CTA is now
enabled** (was blocked-until-verified before).
- **Rejected step:** make a step return `rejected` (mock) → its row shows **the rejection reason** and a
working re-upload/re-submit path; the publish CTA stays blocked.
- **Quality:** `npm run check` and `npm run test:ci` pass; toggle locale → RTL/strings flip correctly;
dark mode renders; React Query Devtools shows one `verification.status` query feeding both B3 and B6 and
invalidating on each mutation.
## 8. Hand off & document (close the phase)
- **Docs:** update the *Project Structure* tree in [`../../../client/CLAUDE.md`](../../../client/CLAUDE.md)
for the nurse verification route subtree, `services/verification`, and the new shared components
(`<DocumentUpload>`, `<TrustBadge>`); add a one-line note that the trust badge is the reusable component
f6 consumes. If you discover a verification rule the product docs don't capture, record it in
[`../../../product/business/02-nurse-verification.md`](../../../product/business/02-nurse-verification.md)
(don't invent — record decisions).
- **Contracts:** **consume** [`../../contracts/domains/verification.md`](../../contracts/domains/verification.md)
(frontend produces none). Any missing/ambiguous shape (e.g. whether credential submit is one endpoint or
split, the exact document-upload flow, the badge payload) → append to
[`../../shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md);
do **not** edit backend files.
- **Handoff & report:** append your phase summary to
[`../../shared-working-context/frontend/STATUS.md`](../../shared-working-context/frontend/STATUS.md);
write [`../../shared-working-context/reports/frontend-phase-5-report.md`](../../shared-working-context/reports/frontend-phase-5-report.md)
— what was built, **what is now testable and exactly how** (the §7 steps), what is mocked client-side
(the `services/verification` mock `clientApi`, if used) and how it swaps to the real b6 endpoint, the
contract consumed + gaps filed, and the follow-up that f6 reuses the `<TrustBadge>`. Update the
[mock registry](../../shared-working-context/reports/mocks-registry.md) for any client-side mock.
- **Memory:** save a `project` memory note for the non-obvious decisions — the data-driven step rendering,
the single-status-query-two-views (B3/B6) caching choice, the reusable `<TrustBadge>`/`<DocumentUpload>`
seams, and the publish-gate wiring — with a one-line pointer in `MEMORY.md`.