281 lines
24 KiB
Markdown
281 lines
24 KiB
Markdown
# UI Phase 4 — Customer storefront
|
||
|
||
> **Mission:** turn home → search → results → nurse profile from functional screens into a **storefront that
|
||
> answers "would I let this person into my mother's home?"**. The data layer already fetches the trust signals
|
||
> (gender, completed visits, verification badge, ratings) — the UI drops most of them on the floor, ships a dead
|
||
> search bar, a fake sort control, a Gregorian date picker in a Shamsi product, and a primary CTA buried under an
|
||
> infinite review list. This phase fixes the funnel's honesty defects and builds the reusable trust-presentation
|
||
> components (`VerificationPanel`, the tappable `TrustBadge` explainer) that phase 8's public-profile preview reuses.
|
||
>
|
||
> **Track:** frontend · **Depends on:** [Phases 0–2](ui-phase-2-shells-and-navigation.md) ·
|
||
> **Unlocks:** [Phase 5 (booking funnel)](ui-phase-5-booking-lifecycle.md) + [Phase 8](ui-phase-8-nurse-business-and-verification.md) (reuses the trust components built here)
|
||
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md) and invoke the frontend-designer skill — both are mandatory.**
|
||
|
||
## 1. Context — where this sits
|
||
|
||
The storefront is the four customer discovery screens: `client/src/app/[locale]/(private-routes)/(customer)/page.tsx`
|
||
(A5 home), `search/page.tsx` (C1 filters), `search/results/page.tsx` (C2 results), and
|
||
`search/nurse/[nurseId]/page.tsx` (C3 profile). Phases 0–2 already de-startered the theme, primitives, and chrome —
|
||
this phase redesigns the route tree itself. Diagnosed defects (all verified in code, full evidence in
|
||
[audit/customer-storefront.md](audit/customer-storefront.md)):
|
||
|
||
1. **The home search bar is a dead affordance.** `HomeSearchBar` pushes `?q=<query>` (page.tsx L130) but
|
||
`SearchFilterScreen` reads only `category_id` (search/page.tsx L49–50) and silently discards `q`. The placeholder
|
||
promises «جستجوی خدمت یا پرستار…» and the input does nothing.
|
||
2. **A failed patients query bricks the home forever.** The gate `if (data == null || isEmpty) return <AppLoading />`
|
||
(page.tsx L66–68) has no `isError`/retry branch — a transient API failure leaves the app's front door on a spinner.
|
||
3. **The patient-record nudge renders unconditionally forever** (page.tsx L96–102), unlike the profile nudge which is
|
||
gated on `hasCustomerProfile` (L103).
|
||
4. **The C1 visit-date filter is a native Gregorian `type="date"`** (search/page.tsx L104–110) in a product where
|
||
every displayed date is Shamsi; the gender facet re-implements an inline `ToggleButtonGroup` (L86–100) instead of
|
||
the shared `GenderToggle`; the live-count CTA is the last child of a long scrolling form (L130–140).
|
||
5. **C2 ships a fake sort** — a `TextField select` with one hard-coded `MenuItem`, `value="rating"`, no `onChange`
|
||
(results/page.tsx L67–69) — and no filter recap: `backToFilters` renders only inside the EmptyState (L58/L88), so
|
||
editing filters from a populated list means browser-back.
|
||
6. **`NurseResultCard` is information-thin.** The result row IS a bookable variant, but the card never names the
|
||
service — three variants of one nurse render as identical cards differing only in price. `nurseGender` and
|
||
`totalCompletedBookings` are **served by the real backend today** (REQ-012 delivered them onto the index row — see
|
||
`services/search/apis/clientApi.ts` `NurseSearchResultDto`) and never rendered. The variant display name is a
|
||
genuine wire gap: neither `NurseSearchResult` (types.ts) nor the DTO carries it.
|
||
7. **C3 doesn't read as a dossier.** The «درخواست رزرو» CTA is the last page child (nurse/[nurseId]/page.tsx L92–101)
|
||
— below the infinite reviews list when that tab is open — and not sticky. `totalCompletedBookings` is fetched and
|
||
never shown; `TrustBadge` is a static chip with no affordance to learn *what* was verified; and the profile DTO
|
||
does **not** serve gender (`clientApi.ts` stubs `nurseGender: 'female'` with an "unused" comment — never render it).
|
||
8. **The empty-results copy is nonsense**: `search.empty_suggest_city` = «شهر نزدیک دیگری مانند مشهد، اصفهان یا شیراز
|
||
را امتحان کنید» (fa.json L361) — a family cannot "try Shiraz"; the patient lives where they live.
|
||
|
||
**What already exists (do not rebuild):**
|
||
|
||
- The search architecture: **the filter object IS the URL IS the React Query cache key**
|
||
(`services/search/keys.ts` + `filterParams.ts`), `keepPreviousData`, debounced Toman price inputs, live count on C1.
|
||
- The **real** search backend (`USE_SEARCH_MOCK = false` since refinement-phase-4): the index row serves
|
||
`nurseName`/`avatarUrl`/`distanceKm`/`nurseGender`/`totalCompletedBookings`; `GET nurses/{id}/profile` serves
|
||
bio/years/services/latestReview.
|
||
- The public trust-badge read: `useNurseTrustBadge(nurseId)` → `GET nurses/{id}/trust_badge` →
|
||
`{ isVerified, approvedAt, credentialTypes[] }` (`services/verification`) — real data for the explainer.
|
||
- Phase 0's theme/brand/icon registry; phase 1's primitives (EmptyState/ErrorState kit, PageHeader, `<Money>`,
|
||
**Jalali date picker**, skeleton twins pattern, RatingInput v2 with honest fractional stars); phase 2's shells,
|
||
safe-area-aware `BottomBar`, and contextual customer header. Consume them; never fork local variants.
|
||
- All four data states on C1/C2/C3, `ProfileSkeleton`, the honest «کل شهر» district semantics, `PriceDisplay` money
|
||
rules, the gender facet's humane hint copy.
|
||
|
||
## 2. Required reading (do this first)
|
||
|
||
- [audit/customer-storefront.md](audit/customer-storefront.md) — the 21 problems + keep-list for this route tree.
|
||
- [audit/feature-components.md](audit/feature-components.md) — `NurseResultCard`/`TrustBadge` findings + the
|
||
component-layer keep-list (presentational purity, caller-owned i18n, token discipline).
|
||
- The four pages under `client/src/app/[locale]/(private-routes)/(customer)/` listed above, plus
|
||
`search/useSearchFilters.ts` and `services/search/{types.ts,filterParams.ts,apis/clientApi.ts}` — know exactly what
|
||
the wire serves before deciding what needs a REQ.
|
||
- `client/src/components/{NurseResultCard,TrustBadge,GenderToggle,ServicePriceRow,PriceDisplay}/` — the components
|
||
this phase owns or extends.
|
||
- `client/src/services/verification/types.ts` (`TrustBadge` wire type, `publicBadgeState`) and
|
||
`hooks/useNurseTrustBadge.ts` — the explainer's data source.
|
||
- [../../../.claude/skills/frontend-designer/SKILL.md](../../../.claude/skills/frontend-designer/SKILL.md) — invoke it;
|
||
and skim `client/CLAUDE.md` "Golden rules".
|
||
- Product rules: [../../../product/overview/platform-summary.md](../../../product/overview/platform-summary.md) (the
|
||
four ground truths), [../../../product/business/04-search-and-matching.md](../../../product/business/04-search-and-matching.md)
|
||
(verified-only, same-gender, variant-is-the-unit), and
|
||
[../../../product/business/02-nurse-verification.md](../../../product/business/02-nurse-verification.md) (what the
|
||
pipeline actually verifies — the explainer must narrate this truthfully).
|
||
|
||
## 3. Scope — build this
|
||
|
||
### 3.1 Customer home — a front door that works
|
||
|
||
- **Fix the dead search bar — decision: convert it into an honest search entry point.** Replace the free-text
|
||
`TextField` with a tappable search affordance (a faux-input `ButtonBase` styled as the search field) that routes to
|
||
C1 (`/search`), optionally focusing the category grid. *Why not client-side text match:* the index has no text
|
||
column, variant names are not queryable client-side, and the only matchable dataset (5–6 cached category names) is
|
||
already better served by the category grid directly below — a half-working text field over-promises exactly where
|
||
trust matters. File the **text-search REQ** (`q` over nurse/variant/category names on `search/nurses`) so the bar
|
||
can upgrade to a real typeahead when the backend serves it; note the upgrade path in the component's JSDoc.
|
||
- **Add the missing `isError` branch**: `usePatients()` failure renders the phase-1 `ErrorState` with retry — never an
|
||
eternal spinner. Keep the onboarding redirect logic (`isEmpty` → onboarding) exactly as is.
|
||
- **Compact trust strip** under the greeting: three icon+label items — «پرداخت امن امانی» · «پرستاران تاییدشده» ·
|
||
«پشتیبانی» — quiet, one row, tokens only. This is ambient reassurance, not a hero; keep it to one line on mobile.
|
||
- **Gate the patient-record NudgeCard on actual record incompleteness.** Derive a completeness signal from the cached
|
||
patients data (e.g. a patient missing conditions/age — inspect `services/patients` types for what's derivable). If
|
||
no honest signal is derivable client-side, show the nudge only when a patient list exists but a patient was never
|
||
opened/completed — and make it dismissible (session-scoped) rather than permanent. Never a forever-nudge.
|
||
- **Rebook shortcut row**: source recent bookings from the existing `useBookingList('customer')` cache
|
||
(`services/bookings`) and render up to 2 «رزرو دوباره با …» cards deep-linking to the nurse's C3 profile. Repeat
|
||
care is the dominant pattern in home nursing. Render nothing (no empty state) when there are no past bookings.
|
||
|
||
### 3.2 Search (C1) — Persian-native filters, visible feedback
|
||
|
||
- **Replace the native `type="date"`** with the phase-1 Jalali controls: a horizontal day-chip strip — «امروز»،
|
||
«فردا»، then day-name + Shamsi date chips for the next 7 days — plus an entry to the full Jalali picker for later
|
||
dates. The value stays the same ISO string `dateIntent` the flow already carries (intent-only, never a hard filter
|
||
— preserve that semantic and its hint copy).
|
||
- **Make the live-count CTA a sticky bottom bar**: «مشاهده N پرستار» pinned above the `BottomBar`, safe-area aware
|
||
(compose with phase 2's safe-area handling — don't reimplement `env(safe-area-inset-bottom)` locally). The count is
|
||
the screen's best feedback instrument; it must be visible while adjusting the upper filters.
|
||
- **Reuse the shared `GenderToggle`** instead of the divergent inline `ToggleButtonGroup`. `GenderToggle` is
|
||
male/female-only by design (required, never defaulted — booking context); extend it with an **opt-in `allowAny`
|
||
prop** (adds the «فرقی ندارد» option) rather than forking, keep the booking-context behavior unchanged, and update
|
||
its co-located test for both modes.
|
||
- **Never render «مشاهده ۰ پرستار» as a tappable CTA.** When the live count is 0, the sticky bar shows a non-CTA
|
||
message («پرستاری با این فیلترها یافت نشد») with the same relaxation hints as 3.6. Full ICU zero-case catalog sweep
|
||
is (DEFERRED → [phase 12](ui-phase-12-copy-motion-and-polish.md)) — just don't ship the zero-CTA here.
|
||
|
||
### 3.3 Results (C2) — honest header, editable filters
|
||
|
||
- **Tappable filter-recap chip row** under the count: category · region (city/district or «کل شهر») · gender · price
|
||
range — each chip deep-links back to C1 **with state preserved**. The filters already live in the URL
|
||
(do-not-regress); C1 must initialize from the carried params, not just `category_id` — extend `useSearchFilters` to
|
||
hydrate from a full `searchParamsToFilters` read.
|
||
- **Kill the fake sort.** Replace the single-option `TextField select` with a static caption «مرتبشده بر اساس
|
||
امتیاز» (the contract's only MVP sort — `SearchSort = 'rating'`). Real sort options only if the API grows them
|
||
(DEFERRED — do not file a REQ for sorts the product hasn't asked for).
|
||
- **Adopt skeleton twins**: replace the generic `Skeleton height={112}` rows with `NurseResultCard.Skeleton`
|
||
(built in 3.4, following phase 1's twin pattern) so loading matches the card anatomy exactly.
|
||
|
||
### 3.4 `NurseResultCard` v2 — the decision card (this phase owns it)
|
||
|
||
Rebuild the card as the four-second decision unit, staying **presentational + memoized** with its co-located test:
|
||
|
||
- **Service/variant display name** — the row IS a variant; the card must name what is being bought. This field is
|
||
missing from the wire (`NurseSearchResultDto` has no display name — verified): **file the REQ** to denormalize
|
||
`variantDisplayName` onto the index row. Until it lands, render the row's category name (available via the cached
|
||
catalog reference data, passed in by the page — the card stays data-agnostic) so multi-variant nurses are at least
|
||
distinguishable by price row; switch to the served name when the REQ lands.
|
||
- **Nurse gender indicator** — load-bearing for same-gender matching; `nurseGender` is served today and never
|
||
rendered. A quiet chip/glyph («خانم»/«آقا» styled per the design language), never color-only.
|
||
- **Completed-visits count** — `totalCompletedBookings` is served today; render «N ویزیت موفق» (fa digits). This
|
||
carries more information than the uniform verified chip (every row is verified by invariant).
|
||
- **Optional top review tag** — one line, e.g. «منظم و دقیق», only if served. The index row carries no review tag
|
||
(verified): file it as an optional field on the same index-row REQ; the card renders it conditionally.
|
||
- **`NurseResultCard.Skeleton`** static twin matching the new anatomy (avatar disc, name+badge row, meta row, price
|
||
row).
|
||
- Keep: `memo`, stable `onSelect`, keyboard/focus a11y, `PriceDisplay` for money, `TrustBadge` for the badge.
|
||
|
||
### 3.5 Nurse profile (C3) — the trust dossier + the shared trust components
|
||
|
||
- **Header identity card**: large avatar, name, gender chip, years of experience, completed visits («N ویزیت موفق» —
|
||
fetched today, never rendered), rating + count. The profile DTO does **not** serve gender (the client stub is a
|
||
placeholder — never render it): **file the REQ** to add `nurseGender` to `NursePublicProfileDto`; until it lands,
|
||
omit the gender chip on C3 (the C2 card and the carried `required_gender` param cover the matching flow honestly).
|
||
- **`VerificationPanel` — new shared component** (`src/components/VerificationPanel/`, barrel + test): "what
|
||
Balinyaar verified" as a check-listed panel, fed by `useNurseTrustBadge(nurseId)` — verified state, `approvedAt`
|
||
(Shamsi), and one row per `credentialTypes[]` entry (stable codes → i18n labels, never raw wire values). Render
|
||
only what is served — no invented steps, no fake dates.
|
||
- **Tappable `TrustBadge` explainer**: give `TrustBadge` an opt-in `onClick`/expand affordance that opens a
|
||
bottom-sheet (mobile) / dialog (desktop) narrating the verification story — «این پرستار این مراحل را گذرانده است…»
|
||
— rendering the same `VerificationPanel` inside. The default (non-interactive) badge everywhere else is unchanged;
|
||
update `TrustBadge.test.tsx`. Wire it up on C2 cards and C3. **File the REQ** for public per-step verification
|
||
detail (step codes + decided dates) so the panel can list identity/Shahkar/license individually with dates; until
|
||
then the panel shows the credential-type rows + the approval date — honest, served data only.
|
||
These two are **REUSED by [phase 8](ui-phase-8-nurse-business-and-verification.md)'s public-profile preview — build
|
||
them shared**, presentational, caller-fed.
|
||
- **Sticky «درخواست رزرو» bottom CTA** (same sticky-bar treatment as 3.2, price-from beside the button) so the CTA
|
||
survives the infinite reviews list. Keep the carried variant/gender/date param handoff to f7 exactly as is.
|
||
- **Reviews tab polish**: aggregate header uses phase 1's RatingInput v2 (fractional — kill the `Math.round`
|
||
overstatement at L260 by consuming the v2 API), review cards get the card-kit treatment. Rating-distribution bars
|
||
are (DEFERRED → phase 12) unless trivially derivable from served data.
|
||
- Render the already-fetched `latestReview` snippet on the services tab if it improves the dossier — optional.
|
||
|
||
### 3.6 Empty-results copy — honest relaxation
|
||
|
||
Replace `empty_suggest_city` in **both** catalogs with honest relaxation suggestions the family can actually act on:
|
||
widen to the whole city («منطقه را خالی کنید تا کل شهر جستوجو شود» — keep/merge with the existing district line),
|
||
try other dates, remove the gender filter. Delete the «مشهد/اصفهان/شیراز» line. Reuse the same relaxation vocabulary
|
||
in the C1 zero-count bar (3.2). Final wording sweep is phase 12's; this phase removes the nonsense and lands
|
||
plausible copy in both `en.json`/`fa.json`.
|
||
|
||
**(DEFERRED)**: public/guest storefront and landing page (→ [phase 13](ui-phase-13-public-front-door.md));
|
||
save/favorite/share nurses (post-MVP, product decision); collapsing multi-variant nurses into one card (needs the
|
||
variant-name REQ served first — note it in the report as a follow-up); C1/C2 Suspense-fallback skeletons beyond what
|
||
phase 1's route-level `loading.tsx` already covers.
|
||
|
||
## 4. Mocks & seams in this phase
|
||
|
||
**None introduced.** Search and reviews are real (`USE_SEARCH_MOCK = false`); verification stays mock-primary but the
|
||
public `trust_badge` read flows through the existing `services/verification` seam either way — this phase only
|
||
consumes hooks. Backend gaps become REQ entries appended to
|
||
[../../shared-working-context/frontend/requests/for-backend.md](../../shared-working-context/frontend/requests/for-backend.md)
|
||
— **REQ-001…038 are taken; check the tracker for the next free number at execution time** (parallel UI phases also
|
||
file). Expected filings from this phase:
|
||
|
||
1. **Free-text search** — `q` param on `GET search/nurses` matching nurse/variant/category names (the home bar's
|
||
upgrade path, 3.1).
|
||
2. **Index-row enrichment** — `variantDisplayName` (required) + optional `topReviewTag` on `NurseSearchResultDto`
|
||
(3.4).
|
||
3. **`nurseGender` on `NursePublicProfileDto`** (3.5 header chip).
|
||
4. **Public verification-step detail** — per-step passed checks + decision dates on (or beside) the `trust_badge`
|
||
payload (3.5 explainer depth).
|
||
|
||
The UI stays mock-tolerant: every new render is conditional on the field being served; nothing blocks on a REQ.
|
||
|
||
## 5. Critical rules you must not get wrong
|
||
|
||
- **Verified-only invariant.** Every returned row is verified by the search-index invariant; the UI **never
|
||
re-filters or re-checks** verification and never fakes a badge state. The explainer *adds* narration to served
|
||
truth — `TrustBadge`'s three honest states (verified/unverified/expired) and their server-driven derivation stay
|
||
exactly as they are.
|
||
- **Never render placeholder data as truth.** The C3 profile's stubbed `nurseGender: 'female'` must not reach the
|
||
screen; no invented verification steps or dates in the panel.
|
||
- **The filter-object-IS-the-URL-IS-the-query-key architecture stays.** Recap chips, C1 hydration, and the sticky CTA
|
||
all read/write the same `filterParams` (de)serializer; deep-linking and back/forward cache hits must keep working.
|
||
- **Money rules**: amounts render only through `PriceDisplay`/the money util (BigInt IRR, Toman at the boundary);
|
||
totals only ever price × sessionCount; never parse money to a float.
|
||
- **Same-gender facet care**: never defaulted, hint copy preserved, the chosen value carried into booking as before.
|
||
- **Design contract**: i18n keys in **both** catalogs; tokens not hexes (`--bal-*` / palette keys only); RTL logical
|
||
props (`marginInlineStart`, `start`/`end`) — verify at `/fa` first; dark mode via tokens; MUI v9 API only; shared
|
||
components (`VerificationPanel`, extended `TrustBadge`/`GenderToggle`/`NurseResultCard`) keep/get co-located tests;
|
||
`clientFetch`/cookies/services rules untouched — this phase adds **no** fetch code outside existing hooks.
|
||
- **Ownership**: phase 0 owns `theme/`/`AppIcon`/`AppButton`; phase 1 owns the shared primitives (Jalali picker,
|
||
RatingInput, state views, skeleton-twin pattern); phase 2 owns `layout/`. If a foundation gap blocks you (e.g. a
|
||
missing icon or sticky-bar primitive), extend the foundation file **minimally** and note it in your report — never
|
||
fork a local variant.
|
||
|
||
## 6. Definition of Done
|
||
|
||
On top of the shared [definition-of-done.md](../../phases/_shared/definition-of-done.md):
|
||
|
||
- [ ] `npm run check` green; `npm run test:ci` green — including updated/new tests for `NurseResultCard` (+ its
|
||
Skeleton), `TrustBadge` (interactive mode), `GenderToggle` (`allowAny`), and `VerificationPanel`.
|
||
- [ ] `en.json`/`fa.json` in sync; «مشهد/اصفهان/شیراز» is gone from both.
|
||
- [ ] The home search affordance routes to C1; a failed patients query shows retry (kill the API to prove it); the
|
||
record nudge no longer renders for a complete record; a customer with past bookings sees the rebook row.
|
||
- [ ] C1: Jalali day-chips replace the Gregorian input; the count CTA is sticky and never tappable at count 0; the
|
||
gender facet is the shared `GenderToggle`.
|
||
- [ ] C2: recap chips deep-link back to C1 with **all** filters preserved (deep-link a full URL to prove hydration);
|
||
no interactive-looking dead sort; skeleton twins during load.
|
||
- [ ] Cards show variant/category label, gender, completed visits; C3 has the sticky CTA, completed visits in the
|
||
header, `VerificationPanel`, and tappable badges opening the explainer; no gender chip on C3 until the REQ lands.
|
||
- [ ] Visual verification on the four axes — `/fa` + `/en` × light + dark — and mobile + desktop for home, C1, C2,
|
||
C3 (sticky bars must clear the BottomBar and the home-indicator safe area on mobile).
|
||
- [ ] REQs filed in the tracker with the next free numbers; no edits outside `client/` and the tracker/report files.
|
||
|
||
## 7. How to test (what a human can verify after this phase)
|
||
|
||
1. **Home**: log in as the seeded customer → the search bar tap opens C1 (no dead `?q=`). Stop the API and reload →
|
||
an error card with «تلاش مجدد», not an eternal spinner. Restart → home recovers; trust strip visible under the
|
||
greeting; a customer with a completed booking sees «رزرو دوباره با …».
|
||
2. **C1**: the date filter shows «امروز/فردا» + Shamsi day chips (no Gregorian browser calendar anywhere). Pick
|
||
filters → the count CTA stays pinned at the bottom while scrolling; set filters matching nothing → the bar shows
|
||
the no-results message and is not tappable.
|
||
3. **C2**: run a search → recap chips show category/region/gender/price; tap one → C1 opens with every filter
|
||
pre-filled; browser-back returns to identical results with zero network (cache hit). The header reads «مرتبشده
|
||
بر اساس امتیاز» as text, not a dropdown.
|
||
4. **Cards**: a nurse with multiple variants shows distinguishable cards (service label + price); every card shows
|
||
gender and «N ویزیت موفق»; tapping the ✓ badge opens the verification bottom-sheet.
|
||
5. **C3**: open a profile → header shows completed visits + rating; `VerificationPanel` lists the served credential
|
||
types + approval date (Shamsi); open the reviews tab and scroll deep → «درخواست رزرو» stays pinned; a 4.5 average
|
||
renders as a fractional star row, not five full stars. Tap the CTA → the C4 request form receives the same
|
||
nurse/variant/gender/date params as before.
|
||
6. Repeat 1–5 on `/en` (LTR) and in dark mode; on a mobile viewport confirm both sticky bars sit above the BottomBar.
|
||
|
||
## 8. Hand off & document (close the phase)
|
||
|
||
- Update `client/CLAUDE.md` "Project Structure": the C1/C2/C3 line items (Jalali chips, sticky CTA, recap chips,
|
||
dossier layout) and the new `VerificationPanel` component entry; note `TrustBadge`'s new interactive mode and
|
||
`GenderToggle`'s `allowAny` on their lines.
|
||
- Write the report at `dev/shared-working-context/reports/ui-phase-4-report.md`: what changed per screen, the exact
|
||
REQ numbers filed (text search, index-row enrichment, profile gender, public step detail), the home-search-bar
|
||
decision + rationale, the multi-variant-collapse follow-up, and any foundation files you extended.
|
||
- Save a memory note per operating-rules §8: phase 4 owns `NurseResultCard`; `VerificationPanel` + the `TrustBadge`
|
||
explainer are the shared trust components phase 8 consumes; the C3 profile DTO still lacks gender (placeholder
|
||
field — never render it) until its REQ lands.
|