ui phase 7
This commit is contained in:
@@ -776,3 +776,87 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
|
||||
- **Proposed shape:** as above.
|
||||
- **Status:** open — invoice renders the reconciling money breakdown + مودیان status unconditionally; the
|
||||
three new rows render only when served.
|
||||
|
||||
## REQ-050 — Nurse inbox decision data: `variantLabel`/`variantPrice`/`variantPriceUnit` on `BookingRequestListItemDto` + an `answered` status-group filter — filed by ui-phase-7 — 2026-07-19
|
||||
- **Need:** Two additions to the nurse's `booking_requests/list` row:
|
||||
1. `variantLabel: string`, `variantPrice: string` (IRR digit-string), `variantPriceUnit: PriceUnit` — the
|
||||
same three fields the single-request `BookingRequestDto` already carries, just missing from the list
|
||||
row.
|
||||
2. Optionally, a status-**group** filter value (e.g. `status=answered`) that server-side unions
|
||||
`accepted_awaiting_payment | converted | rejected_by_nurse` in one paginated query.
|
||||
- **Why:** The redesigned inbox (phase §3.4) leads every card with the service + price — the decision-critical
|
||||
facts — instead of only patient/time/gender. The list DTO has neither field today (confirmed against
|
||||
`services/bookingRequests/types.ts`), so the client falls back to the patient-name headline when absent
|
||||
(mock-tolerant; only the mock's `toListItem` stamps them for now). Separately, the «پاسخداده» tab wants
|
||||
"every request I've already acted on" in one page, but the API filters by a *single* status — the client
|
||||
currently works around this by firing three page-1 queries (`accepted_awaiting_payment`/`converted`/
|
||||
`rejected_by_nurse`) and concatenating them client-side, which is both an N+1-ish fan-out and page-1-only
|
||||
(a nurse with >20 answered-per-status rows can't page into the rest). A `status=answered` value would
|
||||
collapse this to one real paginated query.
|
||||
- **Proposed shape:** `BookingRequestListItemDto { …, variantLabel: string, variantPrice: string,
|
||||
variantPriceUnit: PriceUnit }`; `booking_requests/list?status=answered` as an accepted alias alongside the
|
||||
existing single-status values.
|
||||
- **Status:** open — the mock demonstrates the redesigned card (flip `USE_BOOKING_REQUESTS_MOCK=true`); the
|
||||
real path degrades to the patient-name headline and the three-query «پاسخداده» workaround (documented,
|
||||
page-1-only) until delivered.
|
||||
|
||||
## REQ-051 — Nurse-view address on confirmed+ bookings — filed by ui-phase-7 — 2026-07-19
|
||||
- **Need:** Serve `addressSnapshotJson` (or a nurse-shaped subset including `latitude`/`longitude`) to the
|
||||
**assigned nurse** on `BookingDetailDto` once `status ∈ {confirmed, in_progress, completed, disputed,
|
||||
closed}` — today it is unconditionally masked to `null` for the nurse view (two-stage disclosure correctly
|
||||
keeps it masked pre-confirmation, but the mask never lifts post-confirmation either).
|
||||
- **Why:** The visit-detail address card (phase §3.3) — the "where do I go" a field nurse needs on a
|
||||
confirmed visit, with a `geo:`-URI map deep-link — has nothing to render on the real path once the booking
|
||||
is paid and assigned. The mock now simulates the delivered behavior (`isBookingConfirmedOrBeyond` gates the
|
||||
masking instead of an unconditional nurse-view null) so the card is demonstrable
|
||||
(`USE_BOOKINGS_MOCK=true`); the real `bookingsClientApi` still receives `null` from the server regardless
|
||||
of status, so the nurse UI shows a quiet "available after confirmation" note instead of a hard error or a
|
||||
fabricated address. Also open: whether `recipientPhone` should join the same post-confirmation nurse view
|
||||
(a "call the family" affordance beyond the emergency-contact `tel:` the care-instructions read already
|
||||
provides via `BookingSupportEntry`/`EmergencyBanner`) — flagging for a product decision, not assuming yes.
|
||||
- **Proposed shape:** no DTO shape change — just relax the server-side masking rule on the existing
|
||||
`addressSnapshotJson` field, conditioned on `status` + assigned-nurse tenancy. If a typed shape lands per
|
||||
REQ-045, include `latitude`/`longitude` in it (the map link is presently a best-effort parse of whatever
|
||||
keys the snapshot JSON happens to carry).
|
||||
- **Status:** open — mock-verified only; the real nurse view stays masked (quiet fallback note, no crash)
|
||||
until delivered.
|
||||
|
||||
## REQ-052 — Service label on the today feed — filed by ui-phase-7 — 2026-07-19
|
||||
- **Need:** A `variantLabel: string` field on each `booking_sessions/today` row (`BookingSessionListItemDto`).
|
||||
- **Why:** The day surface (phase §3.2) now renders the service under the patient name on every session card
|
||||
— the "what job is this" a nurse currently only learns by opening the full booking detail. The row has no
|
||||
service/variant field today (confirmed against `services/bookings/types.ts`); the client renders it only
|
||||
when present (mock-tolerant) and deliberately does **not** fetch the booking detail per row to fake it (that
|
||||
would be an N+1 against a list endpoint). The mock stamps it from the seeded booking's frozen variant
|
||||
snapshot.
|
||||
- **Proposed shape:** `BookingSessionListItemDto { …, variantLabel: string }`.
|
||||
- **Status:** open — the mock demonstrates it; the real today feed renders patient name + visit index only
|
||||
until delivered.
|
||||
|
||||
## REQ-053 — Payout forecast: next batch date + expected eligible amount — filed by ui-phase-7 — 2026-07-19
|
||||
- **Need:** Two additions to the nurse earnings-balance read (the REQ-025 summary shape, or a new field on
|
||||
whichever endpoint eventually serves it): `nextPayoutDate: string` (`YYYY-MM-DD`, holiday-shifted
|
||||
server-side) and `nextPayoutEligibleAmountIrr: string` (the amount that batch would pay, ledger-derived).
|
||||
- **Why:** The earnings screen's «برداشت بعدی» line (phase §3.5) and the dashboard's earnings snapshot answer
|
||||
the one question nurses actually ask ("when do I get paid, how much") instead of only the four abstract
|
||||
buckets. Holiday shifting and eligibility are backend truth (per the domain's own load-bearing rule — the
|
||||
client must never compute them); the mock picks a plausible next-batch date (+3 days) and reuses the
|
||||
currently-`eligible` bucket as the forecast amount, both marked optional on the type so the real path
|
||||
(which doesn't serve them yet) renders nothing rather than a fabricated forecast.
|
||||
- **Proposed shape:** `NurseEarningsSummaryDto { …, nextPayoutDate: string, nextPayoutEligibleAmountIrr:
|
||||
string }` (nullable until the first batch is computed, e.g. a brand-new nurse with no eligible earnings).
|
||||
- **Status:** open — mock-only; the forecast line renders nothing on the real path (never computed
|
||||
client-side) until delivered. Folds into REQ-025's existing summary gap.
|
||||
|
||||
## REQ-054 — Web-push for new requests (DEFERRED, non-blocking) — filed by ui-phase-7 — 2026-07-19
|
||||
- **Need:** Push-notification infrastructure (a service worker + a backend push rail behind
|
||||
`INotificationDispatcher`'s deferred SMS/push channels) so a new booking request reaches a nurse whose tab
|
||||
isn't open.
|
||||
- **Why:** The nurse inbox polls every 15s (`BOOKING_REQUEST_POLL_MS`) but the response window is measured in
|
||||
hours — a nurse who isn't looking at the tab when a request arrives can miss it entirely. This is a real,
|
||||
acknowledged product gap (deadline-driven marketplaces live or die on this), but push infra (service-worker
|
||||
registration, VAPID/FCM keys, the backend dispatch rail) is a substantial cross-cutting build in its own
|
||||
right and explicitly out of scope for this phase.
|
||||
- **Proposed shape:** n/a — filed to put the need on record, not to propose an endpoint shape yet.
|
||||
- **Status:** deferred, non-blocking — build nothing for it this phase; the 15s poll remains the only
|
||||
freshness mechanism.
|
||||
|
||||
@@ -119,14 +119,14 @@ the frontend can build before the backend phase merges, and swap to the real HTT
|
||||
| `AddressMapPicker` (map stand-in) | `client/src/components/geography/AddressMapPicker.tsx` | **Not a real map** — a bounded, tappable/draggable marker canvas (CSS grid, no Neshan/Google tiles, no network) that maps the pointer position to `{ latitude, longitude }` around the chosen city's centroid (`CITY_CENTROIDS`/`IRAN_CENTROID` in `services/geography/constants.ts`). Emits real coordinates for the create/update request | _none (component boundary)_ | Replace the canvas internals with a real map widget (Neshan/Google, inlined per the client CSP) that emits the same `{ latitude, longitude }` via `onChange` — `AddressForm` and every caller stay unchanged | 🟡 |
|
||||
| `CatalogApi` | `client/src/services/catalog/apis/mockApi.ts` (+ `apis/seed.ts`) | The catalog skeleton + nurse pricing layer. **Categories mirror the b5 seed exactly** (5 categories, ids 1–5, `sortOrder` 0–4). Seeds representative **option groups/values** the fresh backend does **not** (an admin authors them per category) — incl. required + optional groups and one **cross-category** (`serviceCategoryId=null`) group — so the builder's required-option gate + cross-category rendering demo. Enforces the server's create validation in-memory: `400` missing required dimension / bad price, and the `(nurse, category, option-set)` duplicate **`409`** (via `optionSetSignature`). Variant store seeded **empty** so the offerings empty-state demos; the nurse builds variants live (across price units). `create`/`update`/`set_active`/`list`(active-first, paginated)/`get`. Money stays an **IRR digit-string** end-to-end | `USE_CATALOG_MOCK` (`services/catalog/constants.ts`, default `true`) | b5 `catalog/*` + `nurse_variants/*` are live; set flag `false` — `catalogClientApi` is wired to the action-style routes (camelCase bodies, `pageSize` pagination per REQ-010, `category_id` snake_case filter). **When swapped, categories will have NO option groups until an admin authors them** (the mock's groups were illustrative). No hook/component change | 🟢 (real, refinement-phase-4) |
|
||||
| `VerificationApi` | `client/src/services/verification/apis/mockApi.ts` | The whole nurse trust journey (b6). Seeds the six required steps on `start` (idempotent); `runIdentityKyc` passes any well-formed 10-digit id **except** `0000000000` (→ `failed`/`kyc_no_match`, matches backend `MockIdentityKycProvider`); `runShahkarMatch` requires identity passed, fails **shared-SIM** when the bound national id is `1111111111` (→ `failed`/`shared_sim`); `runBankVerification` passes (assumes a primary bank account); `uploadStepDocument` simulates signed-URL PUT progress then moves the step to `in_review` (metadata only); `submitCredentialDetails` validates the INO number. Re-aggregates like the server (`approved` only when every step passes). **Dev-only** `__mockApproveAll()`/`__mockRejectStep(code,reason)` stand in for the deferred (f15) admin review queue so a human can watch `is_verified`/the trust badge/the publish gate flip — reachable from B3/B6 only while the flag is true | `USE_VERIFICATION_MOCK` (`services/verification/constants.ts`, default `true`) | b6 `nurse_verification/*` + `nurses/{id}/trust_badge` are live; set flag `false` — `verificationClientApi` is wired (action-style routes, camelCase, XHR signed-URL PUT for upload progress + SHA-256 integrity hash). **Caveat:** the real `submitCredentialDetails` no-ops pending REQ-011 (no nurse-facing endpoint for the structured INO/specialties fields yet) — the document uploads it accompanies are contract-backed. No hook/component change | 🟡 |
|
||||
| `BookingsApi` | `client/src/services/bookings/apis/mockApi.ts` | The post-payment engagement (b9). Seeds **2 confirmed bookings** (one 3-session multi-day, one single-visit) + `booking_care_instructions` + a per-session **EVV state machine** — `checkInVisit` flips the session→`in_progress`/`checked_in` (booking→`in_progress`) and computes the **advisory** `checkInAddressMatch` (haversine vs the seeded address ± `MOCK_EVV_TOLERANCE_METERS`, `null` when GPS was absent); `checkOutVisit` requires an open check-in (**`400 no_open_check_in`** otherwise), completes the session (stamps `payoutEligibleAt`), and completes the booking + opens the dispute window once **all** sessions settle. `getCareInstructions` **404s any viewer but the assigned nurse** (the two-stage-disclosure boundary; the UI `enabled` gate means the customer never even calls it). Money stays IRR digit-strings with `gross = commission + payout` and `Σ visitPayout = payout` | `USE_BOOKINGS_MOCK` (`services/bookings/constants.ts`, default `true`) | b9 `bookings/*` + `booking_sessions/*` are live, but a booking only exists after `bookings/convert` runs on a **paid** request — both upstreams (`bookingRequests` mock, card capture b10) aren't real client-side yet. Once conversion is live, set flag `false` — `bookingsClientApi` maps the routes 1:1 (+ `bookingsServerApi` for the RSC prefetch). No hook/component change | 🟢 (real, refinement-phase-4) |
|
||||
| `BookingsApi` | `client/src/services/bookings/apis/mockApi.ts` | The post-payment engagement (b9). Seeds **2 confirmed bookings** (one 3-session multi-day, one single-visit) + `booking_care_instructions` + a per-session **EVV state machine** — `checkInVisit` flips the session→`in_progress`/`checked_in` (booking→`in_progress`) and computes the **advisory** `checkInAddressMatch` (haversine vs the seeded address ± `MOCK_EVV_TOLERANCE_METERS`, `null` when GPS was absent); `checkOutVisit` requires an open check-in (**`400 no_open_check_in`** otherwise), completes the session (stamps `payoutEligibleAt`), and completes the booking + opens the dispute window once **all** sessions settle. `getCareInstructions` **404s any viewer but the assigned nurse** (the two-stage-disclosure boundary; the UI `enabled` gate means the customer never even calls it). Money stays IRR digit-strings with `gross = commission + payout` and `Σ visitPayout = payout`. **ui-phase-7:** `forViewer` now unmasks `addressSnapshotJson` for the nurse once the booking is `confirmed`+ (simulating REQ-051, still delivered) instead of unconditionally nulling it — `addr5001` gained `latitude`/`longitude` matching the EVV reference point so the new address-card map link is demoable; `listTodaySessions` stamps a `variantLabel` off the booking's frozen variant snapshot (REQ-052) | `USE_BOOKINGS_MOCK` (`services/bookings/constants.ts`, default `false`) | b9 `bookings/*` + `booking_sessions/*` are live, but a booking only exists after `bookings/convert` runs on a **paid** request — both upstreams (`bookingRequests` mock, card capture b10) aren't real client-side yet. Once conversion is live, set flag `false` — `bookingsClientApi` maps the routes 1:1 (+ `bookingsServerApi` for the RSC prefetch). No hook/component change. Deliver **REQ-051** (nurse-view address post-confirmation) + **REQ-052** (today-feed `variantLabel`) | 🟢 (real, refinement-phase-4) |
|
||||
| `ILocationProvider` | `client/src/services/bookings/evv/locationProvider.ts` | **EVV GPS capture** — the only client seam f8 introduces. `getCurrentPosition()` never rejects (denied/unavailable → `null`, so a GPS problem is **advisory, never a block**). The **real** provider wraps `navigator.geolocation.getCurrentPosition`; the **mock** returns canned coordinates per mode so the in-range / advisory-out-of-range / denied paths are all demoable without a device (the mock `BookingsApi` computes the match against the same seeded reference point) | `NEXT_PUBLIC_EVV_MOCK_GPS` = `in_range` \| `out_of_range` \| `denied` \| `off` (default `in_range` while `USE_BOOKINGS_MOCK`, else `off`) | Set `NEXT_PUBLIC_EVV_MOCK_GPS=off` (or flip `USE_BOOKINGS_MOCK`) → the real `navigator.geolocation` provider is selected. Real **address-match math** stays server-side (backend geocoding seam), not here — this seam only *captures* the position | 🟢 (real, refinement-phase-4) |
|
||||
| `PaymentApi` | `client/src/services/payment/apis/mockApi.ts` | **The f9 checkout money path** — plays the PSP + webhook roles the client can't reach: `getCheckoutSummary` serves the unserved C6 breakdown (REQ-016; commission-net/VAT/service split via **integer parts-per-10000 BigInt math**, 12% fee / 10% VAT, reconciles to the rial); `initiatePayment` enforces b10 idempotency (same `Idempotency-Key` → same attempt; repeat after capture / lapsed window → **`409`**) and returns a `redirectUrl` into the local mock-gateway harness; `confirmGatewayReturn` on success is the **webhook-confirm stand-in and the missing f7↔f8 bridge** — flips the request `converted` (+ client-augmented `bookingId`, via `mockMarkBookingRequestConverted` in the f7 mock), inserts a **confirmed** booking into the f8 store (`mockInsertConvertedBooking`), and auto-issues the b11-shaped invoice (`moadianStatus: pending`, `pdfUrl: null` so the print path exercises); replayed returns converge idempotently; `getInvoice` 404s until issued. **ui-phase-6:** `initiatePayment`'s `redirectUrl` is now `null` (was a stale pointer to the deleted card-gateway harness page — a latent bug, since the harness itself was already removed in refinement-phase-4; the checkout page's `!redirectUrl` branch already reads the outcome directly, no behavior change); `getCheckoutSummary` adds mock `nurseAvatarUrl: null`/`nurseVerified: true` (REQ-046, the C6 identity moment); the capture path stamps `capturedAt`/`createdAt` on the transaction so `PaymentOutcomeDto` serves `trackingCode`/`paidAt` (REQ-046, the confirmation receipt) and the new `getPaymentHistory` reads the same transaction list (REQ-047, wallet «پرداختها»); invoice creation adds mock `paymentMethod: 'card'`/`transactionReference`/`sellerFiscalIdentity` (REQ-049, fiscal-grade invoice) | `USE_PAYMENT_MOCK` (`services/payment/constants.ts`, default `true`) | b10 initiate + b11 invoice are live and `paymentClientApi` maps them 1:1 (`Idempotency-Key` header, `GET invoices/{bookingId}`); deliver **REQ-016** (checkout summary — the real client already targets the proposed `booking_requests/checkout_summary/{id}` slug) + **REQ-017** (transaction status / `bookingId`; until then the real outcome poll maps `booking_requests/get` statuses and can't distinguish declined from slow) + **REQ-018** (invoice reachable post-capture) + **REQ-046** (nurse identity + tracking code/paid-at) + **REQ-047** (payment history) + **REQ-049** (invoice fiscal fields), make the upstream `bookingRequests` flow real, then set flag `false`. No hook/component change | 🟢 (real, refinement-phase-4) |
|
||||
| Mock-gateway page (test harness) | `client/src/app/[locale]/(private-routes)/(customer)/bookings/checkout/gateway/page.tsx` | **Not a product feature** — a dev stand-in for the PSP's hosted payment page so the initiate → redirect → return round-trip is exercisable without a gateway: the mock `redirectUrl` points here, and its success/failure 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 b10's `redirectUrl` is the PSP's **absolute** URL (the checkout does a full `window.location.assign` for `http(s)` URLs), so this page is simply never linked; delete it when `USE_PAYMENT_MOCK` retires. The PSP's return deep-link into `/bookings/checkout/return` is backend/PSP config | 🗑 removed in refinement-phase-4 (payment flipped real) |
|
||||
| `RefundsApi` | `client/src/services/refunds/apis/mockApi.ts` | **The f10 customer cancel + refund surface** b11 doesn't serve (refunds are admin-only; no customer cancel command, no policy preview, no refund-by-booking, no fee-leg decomposition on the customer status → REQ-019/020/021). Reads the shared **f8 bookings store** (`mockGetBookingForRefund`) to resolve the tier by lead time (`free_24h` >24h / `partial_under_24h` <24h / `customer_no_show` started — client-invented codes → i18n keys) and the per-session refundable(un-started)/locked(completed-and-verified) breakdown, decomposing the refund across the two fee legs via **integer parts-per-10000 BigInt math** (`refundAmount + fee = refundableGross` to the rial). `cancelBooking` flips the booking → `cancelled` (`mockMarkBookingCancelled` stamps the b9 snapshot + cancels only un-started sessions) and creates a refund: **card → `succeeded`** immediately (no ETA); **BNPL → `approved`→`processing`→`succeeded`** over status polls with a `expected_customer_refund_eta` ~10 business days out (Fridays skipped) so the ~7–10-day banner renders. Enforces the outside-policy **`409`** (already-cancelled / nothing-refundable / non-refundable session). Seeds a **`failed`** refund on the cancelled booking 5004 so the contact-support state demos; booking 5002 is pinned to the BNPL channel; booking 5003 (new, mid-engagement) demos the mixed refundable/locked breakdown. Also adds bookings-store seeds 5003/5004 + the two non-seam exports. **ui-phase-6:** `getMyRefunds` (REQ-048) returns every in-memory refund newest-first — the wallet «استردادها» tab | `USE_REFUNDS_MOCK` (`services/refunds/constants.ts`, default `true`) | Deliver **REQ-019** (customer cancel command — the real `refundsClientApi.cancelBooking` already targets `POST bookings/{id}/cancel`) + **REQ-020** (cancellation-policy preview → `GET bookings/{id}/cancellation_policy`, incl. the canonical `cancellation_policy_code` set) + **REQ-021** (`GET refunds/by_booking/{id}` + the decomposition fields on the customer `refunds/{id}/status`) + **REQ-048** (`GET refunds/my`), then set flag `false` — the real client maps the published `refunds/{id}/status` 1:1 and targets the proposed slugs for the rest. No hook/component change | 🟡 |
|
||||
| `BnplApi` | `client/src/services/bnpl/apis/mockApi.ts` | **The f11 BNPL installment checkout (D1–D5)** 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. **ui-phase-6:** was reachable by direct URL in a production build (the equivalent card-gateway harness was already deleted, this one never got the same guard since BNPL stays mock-primary) — now `notFound()`-gated outside `NODE_ENV=development`, still fully reachable in `next dev` | _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 5001–5004 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 | 🟡 |
|
||||
| `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 5001–5004 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. **ui-phase-7:** `buildSummary()` adds `nextPayoutDate` (+3 days) + `nextPayoutEligibleAmountIrr` (= the `eligible` bucket) for the earnings/dashboard «برداشت بعدی» forecast line (REQ-053); the client also gained a `failureReasonLabelKey()` helper (`services/payouts/failureReasons.ts`) mapping known `failureReason` codes (today: `invalid_sheba`) to i18n labels so `PayoutHistoryRow`/the payout detail never show the raw vendor string as the headline | `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) + **REQ-053** (`nextPayoutDate`/`nextPayoutEligibleAmountIrr`), then set flag `false` — `payoutsClientApi` already maps the live `GET nurse_payouts/history` 1:1 and targets the proposed slugs for the rest. 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 | 🟢 (real, refinement-phase-4) |
|
||||
| `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 | 🟢 (real, refinement-phase-4) |
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# UI Phase 7 — Nurse Daily Ops — Report (2026-07-19)
|
||||
|
||||
## What was built
|
||||
|
||||
**The nurse "امروز" dashboard** (`nurse/page.tsx` → `NurseDashboardScreen.tsx`, new) — replaces the
|
||||
`PlaceholderScreen` with pure assembly over already-cached queries:
|
||||
- Greeting header (name off `useMe`) + own `TrustBadge` (`ownBadgeState`, one cached `useVerificationStatus`
|
||||
query, not two).
|
||||
- `NextVisitCard` — first actionable session from `useTodaySessions`, a display-only "starts in" line
|
||||
(`formatRelativeTime`), a check-in shortcut to `/nurse/visits`; a calm «امروز ویزیتی ندارید» empty state.
|
||||
- `RequestsStrip` — the most time-critical widget (sorts above earnings): pending count, the most urgent
|
||||
request's urgency-tinted countdown, an inline open into the request detail.
|
||||
- `EarningsSnapshotCard` — a compact two-stat row (signed net payable + eligible) via `useNurseEarningsBalance`
|
||||
+ `<Money>`; never clamps a negative balance.
|
||||
- `DashboardActivationSlot` (new, exported, page-local) — the **named composition point** for Phase 8. Filled
|
||||
for now with only the existing verification-status banner when not yet approved; renders nothing once
|
||||
approved. **Phase 8: extend `client/src/app/[locale]/(private-routes)/nurse/DashboardActivationSlot.tsx`
|
||||
in place — don't add a second slot.**
|
||||
- `NotificationsEntryRow` — unread count (`useUnreadCount`) linking to `ROUTES.NURSE_NOTIFICATIONS`.
|
||||
- Every widget: skeleton → error-with-retry → empty → data, in that order.
|
||||
|
||||
**Visits day surface** (`visits/page.tsx`):
|
||||
- Shamsi «امروز، {day month}» date anchor via `PageHeader` (replaces the static title).
|
||||
- `useTodaySessions` gained a 60s `refetchInterval` (`TODAY_SESSIONS_REFETCH_MS`) so a same-day schedule
|
||||
change surfaces without re-navigation; the EVV-mutation invalidation is untouched.
|
||||
- `SessionCard` gained an optional `serviceLabel` prop, rendered under the title; the day surface passes
|
||||
`item.variantLabel` (REQ-052, mock-tolerant — `undefined` on the real path just omits the line).
|
||||
- A real error state (`ErrorState` + retry) replaces the old two-field `{ data, isLoading }` destructure that
|
||||
rendered "no visits today" on a failed query.
|
||||
|
||||
**`SessionCard` EVV hero** (shared, both roles — gated on `showEvvControls`):
|
||||
- Check-in/check-out are now the **full-width, ≥48px** primary action (`width:'100%', minHeight:48`),
|
||||
replacing the small `alignSelf:'flex-start'` button visually equal to the "view booking" text link beside
|
||||
it.
|
||||
- Check-out now goes through the new shared `CheckOutConfirmButton` (`components/booking/`, tested) — a
|
||||
lightweight `ConfirmDialog` before firing («اتمام ویزیت؟» — it ends the visit and starts the payout clock).
|
||||
Reused by `BookingDetailView`'s in-visit banner so both entry points confirm the same way.
|
||||
|
||||
**Visit detail workspace** (`BookingDetailView.tsx`, shared both roles):
|
||||
- **Address card** (new, standalone — replaces the old inline hero address line): renders the frozen
|
||||
`addressSnapshotJson` when present (title/city/district/line, best-effort parse — REQ-045 still open), a
|
||||
`geo:{lat},{lng}` map deep-link + a Neshan web-map fallback link when the snapshot carries coordinates
|
||||
(REQ-051), and a quiet nurse-only "available after confirmation" note when masked. Never sourced from the
|
||||
b8 request stage.
|
||||
- **In-visit mode** (new, nurse-only): when a session is `checked_in`, an `AccentCard` state header —
|
||||
«در حال ویزیت» + elapsed on-site time (`formatElapsed` against the server `checkInAt`, never a guessed
|
||||
start) + the check-out CTA **promoted to the top**, via the same `CheckOutConfirmButton`.
|
||||
- **Contact affordance — already delivered, verified, no changes needed.** `BookingSupportEntry`/
|
||||
`EmergencyBanner` (messaging composites, already mounted on `visits/[id]/page.tsx`) already surface a
|
||||
`tel:` click-to-call from the gated care-instructions read for the nurse on a confirmed+ booking. The
|
||||
`phone`/`navigate` icons the phase doc flagged as missing are also already registered
|
||||
(`AppIcon/config.ts`) — both audit findings were stale against current code.
|
||||
- Notes placement: `NurseVisitNotesPanel`/`BookingSupportEntry`/`BookingDetailView` were already each
|
||||
`maxWidth:640, mx:'auto', width:'100%'` and stacked under one `gap:3` — verified consistent, no change
|
||||
needed.
|
||||
|
||||
**Request inbox redesign** (`requests/page.tsx`, `requests/[id]/page.tsx`):
|
||||
- Decision-first `InboxCard`: service + price headline when the list item carries `variantLabel`/
|
||||
`variantPrice` (REQ-050, mock-tolerant — degrades to the patient-name headline on the real path), patient/
|
||||
time/gender as secondary facts. The whole card is now a tappable link (`AppLink`), matching the "fully
|
||||
tappable AccentCard row" convention from the customer bookings screen.
|
||||
- Urgency-tinted countdown pill: `CountdownTimer`'s existing v2 tier API (`warnThresholdSeconds`/
|
||||
`urgentThresholdSeconds`) at teal >2h / amber <2h / terracotta <30min, a label, and `coarseLabel` (which
|
||||
opts into `aria-live="polite"` humanized copy) — no extension to `CountdownTimer` was needed, its ui-phase-1
|
||||
API already covers this.
|
||||
- Tabs + pager: «در انتظار» (`pending_nurse_response`) / «پاسخداده» (client-merged `accepted_awaiting_payment`
|
||||
+ `converted` + `rejected_by_nurse`, **page-1-only — documented limitation**, filed as part of REQ-050's
|
||||
status-group-filter ask) / «منقضی» (`expired_no_response`). `useNurseRequestInbox` gained an optional
|
||||
`enabled` param so the four non-active tab queries don't poll in the background.
|
||||
- Accept now requires a `ConfirmDialog` («با پذیرش، خانواده برای پرداخت دعوت میشود؛ پس از پرداخت، رزرو قطعی
|
||||
میشود.») before firing — the payment-window duration is never hard-coded; after acceptance the detail page
|
||||
renders a `CountdownTimer` against the server `paymentDeadlineAt`.
|
||||
- The inbox's error handling (`isError` → `ErrorState`) was **already correct** in the current code — the
|
||||
phase doc's cited line numbers were stale (a prior phase had already fixed the false-empty defect).
|
||||
|
||||
**Earnings clarity** (`earnings/page.tsx`, `PayoutHistoryRow`, payout detail page):
|
||||
- «برداشت بعدی» `ForecastLine` above the tabs — server-served only (`nextPayoutDate`/
|
||||
`nextPayoutEligibleAmountIrr`, both optional on `NurseEarningsSummary`), renders nothing until served
|
||||
(REQ-053, never computed client-side).
|
||||
- Failure-reason mapping (`services/payouts/failureReasons.ts`, new): known `failureReason` codes (today:
|
||||
`invalid_sheba`) render a mapped Persian/English label as the headline; unknown codes get a generic message;
|
||||
the raw code is always demoted to a secondary `dir="ltr"` caption — never the raw vendor string as the
|
||||
headline. Applied to both `PayoutHistoryRow` and the payout detail page.
|
||||
- `ExplainerCard` a11y: the bare `onClick` `Stack` is now a real `ButtonBase` (`aria-expanded`, `aria-controls`)
|
||||
and the eye icons (`visibilityon`/`visibilityoff`) are replaced by the registered `expand` chevron (rotates
|
||||
180° when open).
|
||||
- Width normalization: adopted one page-level convention — `CONTENT_MAX_WIDTH` (800) + `mx:'auto'` — across
|
||||
every page this phase touches (`requests/page.tsx`, `requests/[id]/page.tsx`, `visits/page.tsx`,
|
||||
`earnings/page.tsx`, `earnings/payouts/page.tsx`, `earnings/payouts/[id]/page.tsx`). The dashboard uses a
|
||||
wider `DASHBOARD_MAX_WIDTH` (960) per the phase's "the dashboard may go wider" allowance. `BookingDetailView`
|
||||
(640) is a component-level width, untouched — a separate decision from the page-level convention.
|
||||
|
||||
**New shared component:** `Pager` (`components/common/Pager/`, tested) — the prev/next "page X of Y" control,
|
||||
replacing three near-identical inline pagers (earnings, payout history, and the new inbox tabs). New `common`
|
||||
namespace i18n keys (`page_prev`/`page_next`/`page_indicator`) so it doesn't depend on the `payouts`
|
||||
namespace's copies (left in place, unused by the new code, but not deleted — out of scope to chase down every
|
||||
call site).
|
||||
|
||||
**REQ-039…043 renumbered to REQ-050…054** — the phase doc was written assuming REQ-001…038 were taken, but
|
||||
ui-phase-3 through ui-phase-6 have since filed REQ-039…049. Filed as REQ-050 (inbox decision data +
|
||||
status-group filter), REQ-051 (nurse-view address post-confirmation), REQ-052 (today-feed service label),
|
||||
REQ-053 (payout forecast), REQ-054 (web-push, deferred/non-blocking) — see `for-backend.md`.
|
||||
|
||||
## What is now testable (and exactly how)
|
||||
|
||||
1. Log in as the seeded verified nurse (refinement-phase-1 demo accounts) → `/nurse` shows a real dashboard:
|
||||
greeting + trust badge, next visit (or the calm empty state), a pending-requests strip, an earnings
|
||||
snapshot, and (if not yet approved) the activation banner.
|
||||
2. `/nurse/visits` (mobile, `/fa`): a Shamsi «امروز، …» header; check-in is a full-width primary CTA; check-out
|
||||
opens a confirm dialog first. Deny browser GPS → check-in still succeeds with the advisory warning toast.
|
||||
3. `/nurse/requests`: three tabs; a pending card leads with service + price **only when
|
||||
`USE_BOOKING_REQUESTS_MOCK=true`** (flip the flag in `services/bookingRequests/constants.ts` — the real
|
||||
path degrades to the patient-name headline); the countdown pill is teal/amber/terracotta by remaining time
|
||||
(seed/adjust a request's deadline to see the escalation). Open a request → «پذیرش» → confirm dialog →
|
||||
accept → the detail shows a payment-window countdown.
|
||||
4. Stop the API (or force a query error) on `/nurse/requests` or `/nurse/visits` → an `ErrorState` panel with
|
||||
retry, never the empty state.
|
||||
5. `/nurse/visits/[id]` with **`USE_BOOKINGS_MOCK=true`** on a confirmed booking (e.g. id `5001` or the
|
||||
completed `5005`) → an address card with a working `geo:`/Neshan-web map link (booking 5001's seed now
|
||||
carries lat/lng matching the EVV reference point); on the real (masked) path → the quiet "available after
|
||||
confirmation" note, no crash. Check in on a today session, then open its booking detail → the «در حال
|
||||
ویزیت» banner with elapsed time + a promoted check-out CTA.
|
||||
6. `/nurse/earnings`: the forecast line always appears (payouts stays mock-primary,
|
||||
`USE_PAYOUTS_MOCK=true` by default); a failed payout (seeded `invalid_sheba`) shows a mapped Persian/
|
||||
English label as the headline with the raw code as a small LTR caption; the explainer header opens with
|
||||
Enter/Space and reports `aria-expanded`.
|
||||
7. Repeat 1–6 on `/en` and dark mode — no stock-MUI colors, no Latin digits in `fa` timers.
|
||||
|
||||
**Verification performed:** `npm run check` (tsc + eslint) is green; `npm run test:ci` is green — 106 suites /
|
||||
455 tests, including the 2 new suites (`Pager`, `CheckOutConfirmButton`) and every touched shared component's
|
||||
existing suite (`SessionCard`, `BookingDetailView`, `PayoutHistoryRow`). A dev-server smoke pass confirmed
|
||||
every touched route (`/nurse`, `/nurse/requests[/1]`, `/nurse/visits[/5001]`, `/nurse/earnings[/payouts[/9001]]`,
|
||||
`/en/nurse`) compiles and responds 200 with no server errors. **Not performed:** a full authenticated
|
||||
click-through in a browser (no browser-automation tool available in this session, and exercising the OTP login
|
||||
flow end-to-end needs the backend API running) — the "How to test" steps above are written for a human to run
|
||||
that pass manually.
|
||||
|
||||
## What is mocked / waiting on a real service
|
||||
|
||||
- `services/bookingRequests` — **real** by default (`USE_BOOKING_REQUESTS_MOCK=false`); the mock's
|
||||
`toListItem` now also stamps `variantLabel`/`variantPrice`/`variantPriceUnit` (REQ-050) so the redesigned
|
||||
inbox card is demonstrable when the flag is flipped for local testing. No registry row (bookingRequests was
|
||||
already de-mocked in refinement-phase-4 and has none — consistent with its siblings).
|
||||
- `services/bookings` — **real** by default (`USE_BOOKINGS_MOCK=false`); mock changes: `forViewer` now
|
||||
unmasks `addressSnapshotJson` for the nurse once `isBookingConfirmedOrBeyond` (simulating REQ-051 ahead of
|
||||
the real endpoint), `addr5001` gained matching `latitude`/`longitude`, and `listTodaySessions` stamps
|
||||
`variantLabel` (REQ-052). See the updated `mocks-registry.md` row.
|
||||
- `services/payouts` — **mock-primary** (`USE_PAYOUTS_MOCK=true`, unchanged — REQ-025 is still the root gap);
|
||||
`buildSummary()` now also serves `nextPayoutDate`/`nextPayoutEligibleAmountIrr` (REQ-053). See the updated
|
||||
`mocks-registry.md` row.
|
||||
- No new seams introduced this phase — every change extends an existing `services/{domain}` mock behind its
|
||||
existing seam.
|
||||
|
||||
## Contracts
|
||||
|
||||
- Consumed: `dev/contracts/domains/{booking-requests,bookings-evv,payouts}.md` (unchanged this phase — no
|
||||
contract landed to consume).
|
||||
- Filed to `for-backend.md`: **REQ-050** (nurse inbox `variantLabel`/`variantPrice`/`variantPriceUnit` + an
|
||||
`answered` status-group filter), **REQ-051** (nurse-view address on confirmed+ bookings), **REQ-052**
|
||||
(service label on the today feed), **REQ-053** (payout forecast), **REQ-054** (web-push, deferred).
|
||||
|
||||
## Docs updated
|
||||
|
||||
- `client/CLAUDE.md` "Project Structure": the nurse route tree (`page.tsx` → `NurseDashboardScreen.tsx` +
|
||||
`DashboardActivationSlot.tsx`, `requests/`, `visits/`, `earnings/`) and the shared component tree
|
||||
(`components/common/Pager/`, `components/booking/`'s `CheckOutConfirmButton` + the `BookingDetailView`/
|
||||
`SessionCard` additions) updated in the same change.
|
||||
- `dev/shared-working-context/reports/mocks-registry.md`: `BookingsApi` and `PayoutsApi` rows updated in place
|
||||
with this phase's mock additions (also corrected `BookingsApi`'s stale "default `true`" config-flag note —
|
||||
it's `false`, real-primary, since refinement-phase-4).
|
||||
|
||||
## Follow-ups for later phases
|
||||
|
||||
- **Phase 8** owns `DashboardActivationSlot`'s content — the fuller "go live" checklist (profile/services/
|
||||
coverage/bank all done), per the phase doc's own hand-off note.
|
||||
- REQ-050's status-group filter (`status=answered`) would collapse the inbox's three-query «پاسخداده» merge
|
||||
into one real paginated query — currently page-1-only, documented in the REQ and in code.
|
||||
- REQ-051 (nurse-view address) is the highest-value of this phase's REQs — until delivered, the address card
|
||||
is demoable only via the mock; REQ-045 (a typed address-snapshot shape) is still open from ui-phase-5 and
|
||||
would let the address card + `.ics` export drop their best-effort JSON-key parsing.
|
||||
- The `payouts` namespace's now-unused `failure_reason_label`/`page_prev`/`page_next`/`page_indicator` i18n
|
||||
keys were left in place (superseded by `failure_code_*` and the shared `common.page_*` keys respectively) —
|
||||
a future cleanup pass could remove them if nothing else references them.
|
||||
- Web-push (REQ-054) remains genuinely deferred — the 15s poll is the only freshness mechanism for new
|
||||
requests until a service-worker + backend push rail is built.
|
||||
Reference in New Issue
Block a user