frontend phase 14
This commit is contained in:
@@ -12,6 +12,45 @@ for awareness.
|
||||
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
|
||||
-->
|
||||
|
||||
## frontend-phase-14-b15 — Messaging (tickets) & notifications — 2026-07-10
|
||||
- **Shipped:** the social/communication layer for the customer **and** nurse apps (role decides chrome, not
|
||||
the components; admin lens DEFERRED to f15). Two new domains — **`services/tickets`** (`useMyTickets` /
|
||||
`useTicket` = the whole thread in one `detail(id)`, no message pagination / `useTicketThread` = select over
|
||||
detail / `useOpenTicket` / `usePostMessage` **optimistic, draft-preserving**, reconcile by `clientMessageId`)
|
||||
and **`services/notifications`** (`useNotifications` unread-first / `useUnreadCount` the **polled** bell —
|
||||
count-only, stale-while-revalidate / `useMarkNotificationRead` + `useMarkAllRead` optimistic `setQueryData`).
|
||||
Screens shared by both shells: **My Tickets inbox** (`/support/tickets`, `/nurse/support/tickets`) with
|
||||
prominent `referenceCode` + unread indicator + null-safe linked-entity hint + Contact-support dialog (shows the
|
||||
new `referenceCode`); **thread** (`/…/tickets/[id]`) role-aware bubbles + sticky composer; **notification
|
||||
center** (`/notifications`, `/nurse/notifications`) unread-first, mark-read-on-open + mark-all, deep-links via
|
||||
`notificationDeepLink`. **Notification bell** in the customer TopBar + the nurse shell (subscribes to the poll so
|
||||
only it re-renders). **Emergency banner** + **"Get support / Open ticket"** on the f8 booking detail
|
||||
(`BookingSupportEntry`, reuses the cached booking + nurse-gated care query — no refetch); a support icon in the
|
||||
customer TopBar + a Support item in the nurse sidebar. New shared/tested composites: `MessageBubble`,
|
||||
`TicketListCard`, `EmergencyBanner`, `NotificationRow`, `NotificationBellView`, `ContactSupportDialog` (+
|
||||
screens); `notificationDeepLink`/`parseNotificationData` unit-tested; `support`/`send` icons; `tickets` +
|
||||
`notifications` i18n namespaces + `nav.support` (both locales).
|
||||
- **Consumes:** dev/contracts/domains/messaging-notifications-admin.md (b15 tickets) + config-reference.md (b1
|
||||
notifications) + openapi/swagger.v1.json. Real & mapped 1:1 by the client APIs: `POST/GET /tickets`,
|
||||
`GET /tickets/{id}`, `POST /tickets/{id}/messages`; `GET notifications/get_notifications`,
|
||||
`get_unread_count`, `POST mark_notification_read` / `mark_all_read`.
|
||||
- **Critical rules honoured:** `is_internal` is **never** modelled in the user-app types — both API mappers DROP
|
||||
any internal message (server-strip mimic), no internal affordance anywhere; the emergency surface is a
|
||||
**post-confirmation `tel:` playbook only** (nurse-gated care contact, no VoIP seam, never a general phone);
|
||||
the unread **count** polls politely (60s interval + 45s staleTime + refetch-on-focus, auth-gated) and the list
|
||||
is never polled; `data_json` is parsed into a typed union and degrades to no-deep-link.
|
||||
- **Mocked client-side:** `services/tickets` (`USE_TICKETS_MOCK`) — b15 is live and `ticketsClientApi` maps it
|
||||
1:1, but the linked bookings are mock-primary and the summary lacks `unreadCount`/`lastMessageAt` (REQ-028);
|
||||
`services/notifications` (`USE_NOTIFICATIONS_MOCK`) — b1 is live and mapped 1:1, but nothing dispatches
|
||||
notifications client-side yet. Both default `true`; swap is one flag. See mocks-registry.
|
||||
- **Gate:** npm run check green · npm run test:ci green (66 suites / 289 tests, +32) · production build compiles
|
||||
+ type-checks clean (a **pre-existing** "Missing .env variable" prerender guard fails on `/en/addresses` +
|
||||
`/en/nurse/verification/identity` only — unrelated to this phase; sibling pages under the same modified shells
|
||||
prerender fine). 6-dimension adversarial review with per-finding verification.
|
||||
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-028: `unreadCount`+`lastMessageAt` on the
|
||||
ticket summary, a message author label / masked-name confirmation, a by-booking user ticket lookup, and an
|
||||
optimistic `clientMessageId` idempotency field).
|
||||
|
||||
## frontend-phase-13-b14 — Reviews & patient care records — 2026-07-10
|
||||
- **Shipped:** the last feature-domain phase. Two new domains — **`services/reviews`** (`useNurseReviews`
|
||||
infinite published-only aggregate+list / `useReviewEligibility` / `useMyReviewForBooking` / `useCreateReview`
|
||||
|
||||
@@ -456,3 +456,35 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
|
||||
until they land. **Note:** the wireframe's four-tab E2 record is not in the data model — please confirm whether
|
||||
the family-owned record is a real MVP entity or a future addition (the client treats it as forward-looking).
|
||||
- **Status:** open
|
||||
|
||||
## REQ-028 — Ticket inbox enrichment (unread + last-activity), message author name, by-booking lookup, optimistic idempotency — filed by frontend-phase-14-b15 — 2026-07-10
|
||||
- **Need:** Four additions the f14 messaging UI renders that the b15 `TicketSummaryDto`/`TicketThreadDto`/message
|
||||
routes don't yet expose (the two ticket screens work today because `services/tickets` is **mock-primary** —
|
||||
`USE_TICKETS_MOCK = true`; the real `ticketsClientApi` maps every live b15 route 1:1 and leaves these blank):
|
||||
1. **`unreadCount` + `lastMessageAt` on `TicketSummaryDto`.** The inbox card shows an **unread indicator** and
|
||||
sorts/labels by **last activity**, but the wire summary carries only `createdAt` (no unread, no last-message
|
||||
time). Today the client falls back to `createdAt` and hides the unread badge on the real path; the mock
|
||||
supplies both. Proposed: `TicketSummaryDto { …, lastMessageAt: string (UTC), unreadCount: int }` (unread =
|
||||
messages after the caller's `last_read_at`, internal excluded).
|
||||
2. **A message author display label on `TicketMessageDto`** (or on `TicketParticipantDto`). The thread bubble
|
||||
labels each message by author, but the DTO carries only `senderId` — no name. The client derives the label
|
||||
from the participant **role** (`author_customer`/`author_nurse`/`author_support`/`author_system`) and never
|
||||
shows a raw identity (privacy — the thread is deliberately not a contact directory). Please **confirm this
|
||||
role-label approach is intended**, or add a **server-masked** display name if a name is wanted. (No phone /
|
||||
no real name is the safe default.)
|
||||
3. **A user-facing by-booking ticket lookup/filter.** `GET /tickets` (the user list) filters only by
|
||||
`Status`/`ReferenceCode` — there's no `bookingId` filter (only the **admin** list has one). The
|
||||
"Get support" action on a booking is specced to **jump to the existing coordination ticket** if one exists;
|
||||
without a by-booking read the client can't find it. The mock approximates this via idempotent
|
||||
`openTicket(category:'coordination', bookingId)` (returns the existing thread). Proposed: add a `bookingId`
|
||||
query param to `GET /tickets`, or `GET /tickets/by_booking/{bookingId}?category=coordination`.
|
||||
4. **An idempotency / `clientMessageId` field on `POST /tickets/{id}/messages`.** The optimistic send generates a
|
||||
`clientMessageId` to reconcile the pending bubble; the server has no field for it, so a retried send (network
|
||||
flap) could create a duplicate server-side. The client does **not** send it today (reconciliation is
|
||||
client-only). Proposed: accept an optional `clientMessageId`/`idempotencyKey` on the post-message body and
|
||||
dedupe on it, echoing it back on `PostMessageResult`.
|
||||
- **Why:** the f14 inbox (unread + last-activity), the thread (author labels + jump-to-coordination), and the
|
||||
optimistic composer render these; the domain is mock-primary precisely because (1)/(3) aren't served and the
|
||||
linked bookings are themselves mock-primary. When they land the swap is a single `USE_TICKETS_MOCK = false` flip
|
||||
(no hook/component change) — `ticketsClientApi` already maps the live routes.
|
||||
- **Status:** open
|
||||
|
||||
Reference in New Issue
Block a user