add build development phases
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
# Frontend Phase 6 — Search & discovery (find a verified, same-gender nurse)
|
||||
|
||||
> **Mission:** build the family-facing **discovery** experience — the heart of the marketplace. A
|
||||
> customer picks a care category, narrows by city / gender / price, and gets a rating-sorted list of
|
||||
> **only verified, accepting** nurses; opening one shows their trust badges, attribute chips, priced
|
||||
> services, and latest review, ending in the **"درخواست رزرو"** call-to-action that hands off to the
|
||||
> booking flow. This phase implements wireframe screens **C1 (search & filter)**, **C2 (results)**,
|
||||
> and **C3 (nurse profile)** against the `search` domain (backend phase b7), and establishes the shared
|
||||
> **nurse-result card** + **price-row** components that the booking flow will reuse.
|
||||
>
|
||||
> **Track:** frontend · **Depends on:** [frontend-phase-4-b5](./frontend-phase-4-b5.md) (catalog browse
|
||||
> & service builder) · [frontend-phase-5-b6](./frontend-phase-5-b6.md) (verified-nurse / trust badge) ·
|
||||
> backend **b7** contract ([`dev/contracts/domains/search.md`](../../contracts/domains/search.md)) ·
|
||||
> **Unlocks:** [frontend-phase-7-b8](./frontend-phase-7-b8.md) (booking request flow)
|
||||
> **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 pivot of the customer journey: everything before this phase let a family *enter* the app
|
||||
(auth f1), describe *who needs care* (onboarding f2), say *where* (addresses f3), and *browse the catalog*
|
||||
(f4). This phase is the first time a family sees **real nurses** and chooses one. It is the screen the
|
||||
product calls the trust funnel: discovery surfaces **only platform-vetted, same-gender-filterable**
|
||||
caregivers, which is Balinyaar's entire differentiation versus opaque incumbents. The output of this
|
||||
phase — a selected nurse + the carried filter intent (especially `required_caregiver_gender`) — is the
|
||||
input to the booking request (f7).
|
||||
|
||||
**What already exists (do not rebuild) — link, extend, never re-create:**
|
||||
- **f0 foundations** ([frontend-phase-0](./frontend-phase-0.md)): the customer mobile shell with the
|
||||
5-tab bottom nav (خانه/رزروها/بیماران/کیفپول/پروفایل), the `services/{domain}` + TanStack Query
|
||||
caching pattern (`keys.ts` factory, `apis/clientApi.ts`, one-hook-per-file, mutation invalidation),
|
||||
the **money/format util** (`formatIrrToToman`, integer-safe IRR-string parse, Shamsi date display) in
|
||||
`src/utils/`, the i18n namespace conventions (incl. the `search` namespace), the RTL baseline, and the
|
||||
shared composite primitives (status chip, stepper, etc.). **Copy the `auth` service shape — do not
|
||||
invent a new data pattern.**
|
||||
- **f4 catalog** ([frontend-phase-4-b5](./frontend-phase-4-b5.md)): the `catalog` service + the
|
||||
**category grid** (مراقبت سالمند / پرستار کودک / تزریقات و سرم / فیزیوتراپی) and category cards used on
|
||||
the customer Home (A5). C1 **reuses that category grid component** and the catalog query — do not
|
||||
rebuild category fetching here.
|
||||
- **f5 verified-nurses** ([frontend-phase-5-b6](./frontend-phase-5-b6.md)): the **trust badge**
|
||||
component(s) — ✓ تاییدشده (verified) and نظام پرستاری (INO membership) — and the verification-status
|
||||
vocabulary. C2/C3 **reuse those badges**; do not re-implement the verified mark.
|
||||
- **Money/format:** prices render through the f0 `formatIrrToToman` util (IRR Rials string → Toman
|
||||
display). Never format money inline.
|
||||
|
||||
> **Data note:** b7's `GET /search/nurses` reads the denormalized `nurse_search_index`, which by
|
||||
> invariant contains a row **only** when the nurse is `is_verified` AND not suspended AND
|
||||
> `is_accepting_bookings` AND the variant `is_active`. So *every* result you receive is already
|
||||
> bookable — the UI must not need to re-filter for verification. (See §5.)
|
||||
|
||||
## 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) — the engineering contract (RSC/client
|
||||
boundary, the `[locale]` layout rule, i18n, theme/tokens, `clientFetch`/`serverFetch`, the
|
||||
`services/{domain}` layout, TanStack Query setup, the cookie manager). Don't break the boundary.
|
||||
- **Invoke the `frontend-designer` skill** before any visual work — it is the design/brand contract
|
||||
(palette: teal `#1d4a40`, terracotta `#d98c6a`, cream; tokens; typography; the `App*` library; the
|
||||
mobile shell; the hard UI rules). C1/C2/C3 must come out branded, RTL, dark-mode-ready. **All visual
|
||||
work goes through it.**
|
||||
- [`../../../product/wireframes/index.html`](../../../product/wireframes/index.html) — Section **C**
|
||||
(screens **C1**, **C2**, **C3**): the exact layout, copy, and controls you implement. C1 = selected
|
||||
category + filter pills (تهران/location, تاریخ/date, جنسیت/gender) + category grid + "مشاهده ۲۴ پرستار"
|
||||
results CTA; C2 = result count + "مرتبسازی: امتیاز" sort + nurse cards (photo, name, ✓ تاییدشده,
|
||||
rating/review count, distance km, "from X تومان/ساعت"); C3 = avatar, name, rating, verified + نظام
|
||||
پرستاری badges, attribute chips, services-and-prices rows, latest review snippet, "درخواست رزرو".
|
||||
- [`../../../product/business/04-search-and-matching.md`](../../../product/business/04-search-and-matching.md)
|
||||
— the business rules: category + city/(optional district) geo search, rating sort, and the
|
||||
**same-gender caregiver** near-hard requirement (`nurse_gender` filter + the requested
|
||||
`required_caregiver_gender` carried *before* booking, not after).
|
||||
- **The contract** [`../../contracts/domains/search.md`](../../contracts/domains/search.md) (b7) +
|
||||
[`../../contracts/conventions/money-and-types.md`](../../contracts/conventions/money-and-types.md) and
|
||||
[`api-conventions.md`](../../contracts/conventions/api-conventions.md) — the envelope, the
|
||||
IRR-as-string money rule, `gender` = `male`/`female`/`any`, `price_unit` =
|
||||
`per_hour`/`per_session`/`per_half_day`/`per_day`/`per_24h`, enums-as-codes (labels are i18n keys,
|
||||
never derived from the code), pagination params, and the exact request/response shapes for
|
||||
`GET /search/nurses` and the nurse-profile/variant payloads. **Types come from this doc — do not guess
|
||||
server shapes** (if a shape is missing, follow §4 + operating-rules §6).
|
||||
- The backend handoff [`../../shared-working-context/backend/handoff/after-backend-phase-7.md`](../../shared-working-context/backend/handoff/after-backend-phase-7.md)
|
||||
— which endpoints are live, what's mocked, what to consume.
|
||||
- The existing **`src/services/auth/*`** (the template) and the **f4 `catalog`** + **f5 trust-badge**
|
||||
code — the patterns you copy and the components you reuse.
|
||||
|
||||
## 3. Scope — build this
|
||||
|
||||
A vertical slice: `services/search` (types/keys/apis/hooks) → the three screens (C1, C2, C3) → the two
|
||||
shared components (nurse-result card, price-row), all RTL/i18n/cache-correct.
|
||||
|
||||
### 3.1 The `search` domain service (`src/services/search/`)
|
||||
Copy the f0/`auth` service shape exactly:
|
||||
- **`types.ts`** — mirror the b7 contract as string-literal unions / interfaces (don't guess):
|
||||
- `NurseSearchFilters` — `serviceCategoryId: number`, `cityId: number`, `districtId?: number`,
|
||||
`nurseGender?: 'male' | 'female'` (omit = any), `priceMin?: string`, `priceMax?: string` (IRR
|
||||
strings), `sort: 'rating'` (the only MVP sort), `page`, `pageSize`.
|
||||
- `NurseSearchResult` — the C2 card row: `nurseId`, `variantId`, `displayName`/`nurseName`,
|
||||
`avatarUrl`, `isVerified` (always true by invariant), `averageRating`, `totalReviews`,
|
||||
`distanceKm?`, `priceFromIrr` (string), `priceUnit` (the `per_*` union), `nurseGender`.
|
||||
- `NurseProfile` — the C3 payload: identity (`nurseName`, `avatarUrl`, `bio`, `yearsExperience`),
|
||||
`averageRating`/`totalReviews`/`totalCompletedBookings`, `isVerified`, `inoMembership` (نظام پرستاری
|
||||
badge flag), `attributeChips` (specialties/تخصصها labels), `services: NurseProfileServiceRow[]`
|
||||
(each = `variantId`, `displayName`, `priceIrr` string, `priceUnit`, optional `sessionCount`), and a
|
||||
`latestReview?` snippet (`rating`, `body`, `authorMasked`, `createdAt`).
|
||||
- `Paged<T>` — reuse the f0 paginated envelope type.
|
||||
- **`keys.ts`** — the query-key factory: `searchKeys.results(filters)` keyed on the **full filter
|
||||
object** (this is the cache contract — see §5), `searchKeys.profile(nurseId)`.
|
||||
- **`apis/clientApi.ts`** — wraps `clientFetch` (never raw `fetch`):
|
||||
- `searchNurses(filters): Promise<Paged<NurseSearchResult>>` → `GET /search/nurses` (filters as query
|
||||
params; omit absent optional filters so the key/URL stay stable).
|
||||
- `getNurseProfile(nurseId): Promise<NurseProfile>` → the b7 nurse-profile endpoint (consume the exact
|
||||
route from the contract).
|
||||
- Add a **`serverApi.ts`** only if you prefetch C2/C3 from an RSC to remove a client round-trip
|
||||
(optional, see §5).
|
||||
- **`hooks/` (one hook per file):**
|
||||
- `useNurseSearch.ts` — `useQuery({ queryKey: searchKeys.results(filters), queryFn })` with a
|
||||
deliberate `staleTime` (results are read-heavy and change slowly) and `keepPreviousData` so the list
|
||||
doesn't flash empty while a new filter loads.
|
||||
- `useNurseProfile.ts` — `useQuery` on `searchKeys.profile(nurseId)`; `enabled` only when an id is
|
||||
present.
|
||||
- `useDebouncedSearchFilters.ts` (or fold the debounce into the C1 controller) — **debounce the
|
||||
free-text/quick-filter input** so keystrokes don't fan out one request per character.
|
||||
- **`index.ts`** — barrel.
|
||||
|
||||
### 3.2 C1 — Search & filter screen (`جستجو و فیلتر`)
|
||||
The entry screen (reachable from Home/A5 search bar and category tap). Build:
|
||||
- **Selected-category** header + the **category grid** (reuse the f4 catalog category grid;
|
||||
selecting a category sets `serviceCategoryId`). Categories shown per wireframe: مراقبت سالمند، پرستار
|
||||
کودک، تزریقات و سرم، مراقبت زخم (driven by the live catalog, not hardcoded labels).
|
||||
- **Filter pills** (the C1 row): **city** (تهران ▾ — required; the cascading province→city→district
|
||||
picker reused from f3 geo — district optional, "leaving district empty searches the whole city" helper
|
||||
copy), **date** (تاریخ ▾ — capture intent only; availability is **soft** at MVP and is *not* a hard
|
||||
search filter — pass it through to booking, don't filter results on it), **gender** (جنسیت ▾ — خانم /
|
||||
آقا / فرقی ندارد → `male`/`female`/omit). Make **gender a prominent, early, first-class control** with
|
||||
one line of copy on why same-gender matters for bodily care.
|
||||
- An optional **price-range** control (min/max → IRR strings) and the **results CTA** that mirrors the
|
||||
wireframe's "مشاهده ۲۴ پرستار" — i.e. show the live result **count** and navigate to C2. (Wire the
|
||||
count off a lightweight `useNurseSearch` head/`totalCount`, or navigate and show the count on C2 — your
|
||||
call, but the number must be real, not hardcoded.)
|
||||
- Hold filter state in a small **colocated controller** (a `useSearchFilters` hook or local reducer) —
|
||||
*not* in a high context provider (it changes fast). The filter object is what becomes the query key.
|
||||
|
||||
### 3.3 C2 — Results screen (`نتایج جستجو`)
|
||||
- **Header:** result **count** ("۲۴ پرستار") + **sort control** "مرتبسازی: امتیاز ▾" (rating is the only
|
||||
MVP sort — render it as a control but it has one option; tag other sorts **(DEFERRED)**).
|
||||
- **List** of **`NurseResultCard`** (§3.5) rendered from `useNurseSearch(filters)`, paginated (infinite
|
||||
scroll or a "load more" — reuse the f0 paginated pattern). The customer **bottom tab nav** stays
|
||||
visible (this is a shell screen).
|
||||
- **States (all required):** **loading** = skeleton cards (not a spinner); **empty** = the "no nurses
|
||||
match → relax your filters" state with concrete suggestions (remove the gender filter / widen to whole
|
||||
city by clearing district / try a nearby city — lean on the white-space cities Mashhad/Isfahan/Shiraz);
|
||||
**error** = retry; **populated** = rating-sorted cards. Tapping a card → C3.
|
||||
- Changing a filter on C1 and returning re-queries with the new key; **reverting to a prior filter set
|
||||
reuses the cached result** (no refetch) — this is an acceptance criterion (§5, §7).
|
||||
|
||||
### 3.4 C3 — Nurse profile screen (`پروفایل پرستار`)
|
||||
From `useNurseProfile(nurseId)`:
|
||||
- **Header:** avatar, name, **rating** (+ review count), and **badges** — ✓ تاییدشده (reuse f5 verified
|
||||
badge) and **نظام پرستاری** (INO membership; render only when `inoMembership` is true).
|
||||
- **Attribute chips:** specialties / experience (سالمندان، تزریقات، ۸ سال سابقه) from `attributeChips` /
|
||||
`yearsExperience`.
|
||||
- **Services & prices:** a list of **`ServicePriceRow`** (§3.5) — one row per offered variant
|
||||
(`displayName` + price rendered via `formatIrrToToman` + the localized `price_unit` label, e.g.
|
||||
"۲۸۰٬۰۰۰ تومان/ساعت", and a 12h night-shift variant). These are the bookable units.
|
||||
- **Latest review snippet** (`latestReview`) — rating + masked author + truncated body; "no reviews yet"
|
||||
empty state. (The full reviews tab is **(DEFERRED)** → [frontend-phase-13-b14](./frontend-phase-13-b14.md).)
|
||||
- **Primary CTA: "درخواست رزرو"** — navigates to the booking request form (f7), **carrying the selected
|
||||
nurse + variant + the filter intent** (especially `required_caregiver_gender` derived from the C1
|
||||
gender filter, and the city/category). f7 owns the form; this phase only hands off the intent — pass it
|
||||
via route params / a small handoff, do **not** build the request form here. Tag the form itself
|
||||
**(DEFERRED → f7)**.
|
||||
- States: loading skeleton, not-found (404 → "this nurse is no longer available"), error/retry.
|
||||
|
||||
### 3.5 Shared components (built once, reused by f7+)
|
||||
At the shared level (`src/components/…`), composed from MUI/`App*` primitives (never re-implement a root
|
||||
primitive), each with a **co-located `*.test.tsx`** and i18n in both locales:
|
||||
- **`NurseResultCard`** — the C2 card: `Avatar` (photo), name, the reused **verified badge**, rating +
|
||||
review count, distance chip (km, only when `distanceKm` present), and the **"from X تومان/ساعت"**
|
||||
price line (via `formatIrrToToman` + localized unit). Pure/presentational, memoized, stable props so a
|
||||
list of N cards doesn't re-render on unrelated state.
|
||||
- **`ServicePriceRow`** — the C3 service line and a reusable price row: localized service name + the
|
||||
money formatting + the `price_unit` label. Reused on C3 now and by the booking summary later.
|
||||
> These two are the load-bearing reusable pieces. The category grid (f4), trust badges (f5), geo picker
|
||||
> (f3), and status chip (f0) are **reused**, not rebuilt.
|
||||
|
||||
### 3.6 i18n
|
||||
Fill the **`search`** namespace (seeded in f0) in **both** `messages/en.json` and `messages/fa.json`,
|
||||
in sync, RTL-first: filter labels (شهر/تاریخ/جنسیت/خانم/آقا/فرقی ندارد), sort label, result-count
|
||||
pluralization, every empty/error/loading copy, badge labels (تاییدشده/نظام پرستاری), price-unit labels
|
||||
(ساعتی/per_hour … per_24h), and the "درخواست رزرو" CTA. **No display label is derived from an enum code**
|
||||
— each `price_unit`/`gender`/sort code maps to an i18n key.
|
||||
|
||||
## 4. Mocks & seams in this phase
|
||||
|
||||
This is a **frontend** phase — it introduces **no backend seam**; it **consumes** the b7 contract.
|
||||
|
||||
- **Reuse the `services/{domain}` seam pattern from f0.** All data goes through `clientFetch` inside
|
||||
`services/search/apis/`.
|
||||
- **If b7 is not yet merged (or a needed shape is missing):** build a **mock `clientApi`** behind the
|
||||
*same* `services/search` seam (real-shaped fixtures: a handful of verified nurses with ratings,
|
||||
distances, prices, badges; one profile with services + a review) so C1/C2/C3 are fully demoable, **and**
|
||||
(a) append the exact missing/mismatched shape to
|
||||
[`../../shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)
|
||||
(operating-rules §6 — you never edit backend files), and (b) record the mock in your frontend report so
|
||||
it's swapped out cleanly when the real endpoint lands. Selection between mock and real `clientApi` is by
|
||||
the seam (one import swap), never an `if (mock)` scattered through components.
|
||||
|
||||
No new entry is needed in the backend `mocks-registry.md` (that registry is for backend DI seams); the
|
||||
client-side mock is recorded in your **frontend report** instead.
|
||||
|
||||
## 5. Critical rules you must not get wrong
|
||||
|
||||
- **Only verified + accepting nurses appear — and the UI must not have to enforce it.** The b7
|
||||
`nurse_search_index` invariant guarantees every returned row is verified, not suspended, accepting, and
|
||||
on an active variant. **Never** add client logic that *re-includes* hidden nurses, and never display an
|
||||
unverified/paused nurse. If a result somehow lacks the verified flag, treat it as a data defect and file
|
||||
it via `for-backend.md` — do not paper over it.
|
||||
- **The filter object IS the query key — identical filters reuse cache, never refetch.** `queryKey =
|
||||
searchKeys.results(filters)` must be a *stable, canonical* serialization (sorted keys, omitted optional
|
||||
filters rather than `undefined`, IRR as strings). Changing a filter and **reverting** to a previous set
|
||||
must hit the React Query cache with **zero** network calls (verify in Devtools — §7). Use
|
||||
`keepPreviousData` so the list doesn't flash. This is the whole point of the phase's caching design.
|
||||
- **Debounce input.** Free-text / quick-filter typing must **not** fire one request per keystroke —
|
||||
debounce before it becomes part of the query key.
|
||||
- **Same-gender is first-class and carried *before* booking.** The gender filter
|
||||
(`male`/`female`/`any`) is a prominent, early control with explanatory copy; the chosen value is carried
|
||||
into the booking handoff as `required_caregiver_gender` (f7) — surfaced *before* booking, never
|
||||
discovered after. Never default or silently drop gender.
|
||||
- **Geography semantics:** city is required; **district is optional and "empty district = whole city"** —
|
||||
the picker copy must say so; don't send a bogus district. (Backend matches city-only + district rows;
|
||||
the client just leaves `districtId` absent.)
|
||||
- **Availability is soft at MVP — never a hard search filter.** The date pill captures intent for the
|
||||
booking flow; it must not remove nurses from results. Tag availability-window filtering **(DEFERRED)**.
|
||||
- **Money renders through the f0 util only — IRR Rials are integer strings, no floats, Toman is
|
||||
display-only.** Format with `formatIrrToToman`; never parse IRR into a JS number for math, never compute
|
||||
a "from" price client-side beyond picking the min the server sent.
|
||||
- **Enums are codes; labels are i18n keys.** `price_unit`, `gender`, and sort never render their raw
|
||||
code; each maps to a localized label in both `en.json`/`fa.json`.
|
||||
- **RSC/client boundary + caching discipline:** prefetch C2/C3 from an RSC with `initialData` only if it
|
||||
removes a round-trip; otherwise client-fetch. No `next/headers`/`next-intl/server` in client components.
|
||||
- **Minimal re-renders:** `NurseResultCard` is presentational/memoized; keep fast-changing filter state
|
||||
colocated (not in a high provider); use `select` to subscribe to slices where it helps.
|
||||
- **MUI primitives stay MUI; the two new composites live shared** — not inline in a page.
|
||||
|
||||
## 6. Definition of Done
|
||||
|
||||
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
|
||||
- [ ] `services/search` exists (`types`/`keys`/`apis`/`hooks`/`index`), copying the f0 pattern; types
|
||||
derive from the b7 contract (or a mock behind the seam + a `for-backend.md` request if b7 isn't
|
||||
ready).
|
||||
- [ ] **C1, C2, C3** are built per the wireframe, RTL, with category grid (reused), city/gender/(optional
|
||||
district)/price filters, rating sort, and the prominent same-gender control.
|
||||
- [ ] C2 has **all four states** (loading skeletons / empty "relax filters" / error-retry / populated);
|
||||
C3 has loading / not-found / error states.
|
||||
- [ ] **Caching proven:** the filter object is the query key; reverting a filter reuses cache with no
|
||||
network call; input is debounced; `keepPreviousData` set. (Demonstrable in Devtools — §7.)
|
||||
- [ ] `NurseResultCard` + `ServicePriceRow` are **shared** components with co-located tests; the verified
|
||||
badge (f5), category grid (f4), geo picker (f3) are reused, not rebuilt.
|
||||
- [ ] Prices render via the f0 money util; every string is an i18n key in **both** locales, in sync; no
|
||||
label derived from an enum code.
|
||||
- [ ] "درخواست رزرو" hands off the selected nurse + variant + `required_caregiver_gender` + city/category
|
||||
to the f7 route (form itself deferred to f7).
|
||||
- [ ] `npm run check` green; `npm run test:ci` green (the two new shared components are covered);
|
||||
`client/CLAUDE.md` *Project Structure* updated for the new `services/search`, the two shared
|
||||
components, and the C1/C2/C3 routes.
|
||||
|
||||
## 7. How to test (what a human can verify after this phase)
|
||||
|
||||
Run `npm run dev` (point `NEXT_PUBLIC_API_URL` at a b7-enabled server, or use the seam mock if b7 isn't
|
||||
merged):
|
||||
- **End-to-end discovery:** from Home, open **C1** → pick a category (e.g. مراقبت سالمند), set city
|
||||
(تهران), set gender (خانم) → the results CTA shows a real count → tap it → **C2** lists **only
|
||||
verified** nurses, rating-sorted, each card showing photo, name, ✓ تاییدشده badge, rating + review
|
||||
count, distance, and "from X تومان/ساعت". Confirm no unverified/paused nurse ever appears.
|
||||
- **Profile:** tap a card → **C3** shows avatar, rating, ✓ تاییدشده + نظام پرستاری badges, attribute
|
||||
chips, the services-and-prices rows (correct Toman formatting + Persian unit labels), and the latest
|
||||
review snippet; **"درخواست رزرو"** navigates to the f7 route carrying nurse + variant + gender intent.
|
||||
- **Empty state:** search a white-space city/category/gender combo with no matches → the "no nurses match
|
||||
→ relax your filters" state with concrete suggestions (clear district / drop gender / try Mashhad).
|
||||
- **Caching (the headline check):** open React Query Devtools → apply filter set A (cache entry A) →
|
||||
change to set B (entry B, one fetch) → **revert to A** → the list shows instantly with **zero new
|
||||
network requests** (cache hit on key A). Type quickly in the search input → confirm **one** debounced
|
||||
request, not one per keystroke.
|
||||
- **i18n/RTL:** flip locale fa↔en → all C1/C2/C3 labels, badges, unit labels, and empty/error copy
|
||||
translate and mirror correctly; dark mode still renders.
|
||||
- **Gate:** `npm run check` and `npm run test:ci` pass.
|
||||
|
||||
## 8. Hand off & document (close the phase)
|
||||
|
||||
- **Docs:** update the **Project Structure** tree in [`../../../client/CLAUDE.md`](../../../client/CLAUDE.md)
|
||||
for the new `services/search`, the C1/C2/C3 routes/segments, and the two new shared components
|
||||
(`NurseResultCard`, `ServicePriceRow`); note the "filter-object-as-query-key" caching pattern as a
|
||||
reusable convention. Fix any doc drift you touch.
|
||||
- **Contracts:** **consume** [`../../contracts/domains/search.md`](../../contracts/domains/search.md)
|
||||
(b7) — derive `services/search/types.ts` from it; **do not** edit it. Any missing/ambiguous shape (e.g.
|
||||
the nurse-profile services array, the `latestReview` shape, distance units, or the count head) goes to
|
||||
[`../../shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)
|
||||
as an append — the backend delivers it in a later change; you never edit backend files.
|
||||
- **Handoff & report:** append your summary to
|
||||
[`../../shared-working-context/frontend/STATUS.md`](../../shared-working-context/frontend/STATUS.md);
|
||||
write [`../../shared-working-context/reports/frontend-phase-6-report.md`](../../shared-working-context/reports/frontend-phase-6-report.md)
|
||||
— what was built (C1/C2/C3 + `services/search` + the two shared components), **what is now testable and
|
||||
exactly how** (the steps in §7), what (if anything) is **mocked behind the `services/search` seam** and
|
||||
how f-next swaps it for the real b7 endpoint, the contract consumed + any `for-backend` requests filed,
|
||||
and the follow-ups (the f7 booking handoff contract for the carried intent; the C3 reviews tab deferred
|
||||
to f13).
|
||||
- **Memory:** save a `project`-type memory note for the non-obvious decisions this phase locks in — the
|
||||
**filter-object-as-query-key** caching contract, the verified-only invariant the UI relies on (so a
|
||||
future agent doesn't add a client-side verification re-filter), and the **`required_caregiver_gender`
|
||||
carried-before-booking** handoff to f7 — with a one-line pointer added to `MEMORY.md`.
|
||||
Reference in New Issue
Block a user