Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions ARCHITECTURE_ESSENTIALS.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,134 @@ assumed and mirrored in `models.ts` under "Phase W6 — Feature flags console".
- Approaching deadline warnings: prominent alerts when held escrow refund deadline is < 48 hours away and order is unfulfilled (Threat Model T8 compliance).
- Direct explorer links to Stellar testnet transactions (`https://stellar.expert/explorer/testnet/tx/...`).

### Care hub — patient home (#33)
No new backend endpoint and no new assumed shape. The hub is a
client-side aggregation over the two contracts above —
`GET /patients/me/dependents` and `GET /patients/:profileId/health/:section`
— ranked into sections in `src/features/care-hub/ranking.ts`. The derived
`CareCard` is deliberately a presentation-only type (it exists in the
browser and is never sent to or received from `docforum-core`), so it does
not violate hard rule 2.

Two things the issues ask for are not yet wired because their backend
contract does not exist here:
- **There is no way to learn the signed-in patient's own `patientProfileId`.**
`#20`'s profile store is populated only by tests — no response in this
repo carries an `ActingProfile`, and `User` has no profile id — so
`selectedProfileId` is `null` in a real session and every health-profile
query stays disabled. That is a `docforum-core` gap to file (hard rule 1),
not something to paper over by assuming `profileId === userId`. The care
hub reports such a source as `unavailable` rather than loading or failed,
so the screen renders the cards it *can* derive instead of spinning
forever, and never reads "we could not ask" as "you have nothing saved".
The health editor and dependents screens have the same dependency.
- **Referrals awaiting acceptance, orders needing a facility, results
ready, waitlist offers, follow-ups** wait on `docforum-core`#5–#16
(see issues #4 and #5 here). Each will contribute its own card source
when that lands.

"Coming up" is no longer one of those: since #32 it is fed by
`useMyAppointments()`, which reads `docforum-core`'s **real**
`GET /appointments/mine` (see below), so a visit happening today ranks under
"Action needed" and a later one under "Coming up" in a real session — the
appointments source needs only the access token, not a `patientProfileId`.

## Known assumptions — Phase W2 patient follow-ons (#32, #34, #37)

Everything under "assumed" below is a shape this repo *wrote down*, not one
`docforum-core` serves yet; each screen treats a 404 as "not available for
this deployment yet", never as "you have nothing". Reconcile against the real
contract when the backend issues land.

### Appointments — the real part (`docforum-core` Phase 1)
- `GET /appointments/mine` (Bearer access token) → a **bare array**, not an
envelope, newest-first, each item with `availabilitySlot` included
(`{id, doctorProfileId, status, startTime, endTime, createdAt, updatedAt}`).
- `AppointmentStatus` is `scheduled | completed | cancelled | no_show` —
there is **no** `checked_in`/`being_seen` in the real enum, which is why
queue state lives in its own shape below rather than in `status`.
- `AppointmentOrigin` is `direct_booking | referral`; `SlotStatus` is
`open | held | booked`.
- **Still-missing from this real endpoint: no clinician name and no facility
name.** It returns `doctorProfileId` only, so every visit card and the
appointment detail page describe the *time*, never "Dr —". Putting a name
there needs a `docforum-core` change (include the doctor profile, or a
patient-facing summary route); this repo will not resolve a name by
guessing from an id.

### Check-in and live queue (#32, assumed — `docforum-core` #129)
- `GET /appointments/:appointmentId/queue` → `AppointmentQueue`
- `POST /appointments/:appointmentId/check-in` → `AppointmentQueue`
- Both are keyed by an appointment id the patient already owns:
`AppointmentDetailPage` only asks for a queue after matching that id
against the real `GET /appointments/mine` list, so an unfamiliar id reads
"we cannot find that appointment on your account" and never reaches the
queue route at all. Authorising the id itself is still `docforum-core`'s
job.
- `AppointmentQueue` shape: `{appointmentId, phase: 'not_open'|'waiting'|'being_seen'|'completed', peopleAhead: number|null, estimatedWaitMinutes: number|null, checkInOpensAt: string|null, checkedInAt: string|null}`
- `null` in `peopleAhead`/`estimatedWaitMinutes` means "not published", and
is rendered as such — `0` would be a claim about the waiting room.
- Polling, not SSE: `refetchInterval` 15s while the panel is on screen, and
`gcTime: 0` so unmounting the page stops the requests. `docforum-core` has
no SSE endpoint to hook to yet.

### 'Who viewed my records' access log (#34, assumed — `docforum-core` #45)
- `GET /patients/me/access-log?from&to&recordType&cursor` → `AccessLogPage`
- `AccessLogPage` shape: `{events: AccessLogEvent[], nextCursor: string|null}`
- `AccessLogEvent` shape: `{id, occurredAt, actorRole: 'doctor'|'facility'|'patient'|'admin', actorName, recordType: 'health_profile'|'appointment'|'referral'|'order'|'dependent', reason: 'consultation'|'order_fulfillment'|'result_delivery'|'handover_claim'|'support'}`
- The `me` scope is used because, again, the frontend cannot learn its own
`patientProfileId` (the gap above) — an id in the path would have to be
guessed.
- The "report a concern" link is gated on `VITE_SUPPORT_EMAIL` — no address
configured means no link, not a `mailto:` to an invented one.

### Payment history and receipts (#37, partly real)
- **Real today:** the `PaymentIntent` row itself — `{id, orderType, orderId,
status, patientProfileId, facilityId, amountStroops, escrowId,
stellarTxHash, sorobanContractId, createdAt, updatedAt}` — with the BigInt
columns (`amountStroops`, `escrowId`) serialized to **strings**. Those
strings are kept as strings in `models.ts` and converted with `BigInt`
only (`src/features/payments/stroops.ts`); `Number` would round a payment
above 2^53 stroops.
- **Real today, admin-only:** `POST /payments/intents*` and
`GET /payments/intents/:id` require the `admin` role, which is why the
patient screens below are separate.
- Assumed (`docforum-core` #90 has not landed):
- `GET /payments/mine?cursor` → `PaymentHistoryPage`
- `GET /payments/mine/:intentId` → `PaymentHistoryEntry`
- `PaymentHistoryEntry extends PaymentIntent` with optional
`facilityName?: string` and `refundDeadlineAt?: string` — presentation
extras layered *on top of* the mirrored row, not a parallel shape (hard
rule 2). Both are omitted from the UI when absent.
- `patients` routes in `docforum-core` are still a placeholder module, so
every `me`-scoped patient endpoint above is a request, not a report.
- `src/features/payments/explorer.ts` builds testnet explorer links from
`VITE_STELLAR_EXPLORER_BASE_URL` (default
`https://testnet.stellar.expert/explorer/public`). This is display-only:
nothing here queries Stellar (hard rule 1) — it turns a hash
`docforum-core` already returned into a URL.

### Cursor pagination (assumed across #34 and #37)
`docforum-core` has **no** cursor-pagination helper in `main` — the one
patient list that does exist, `GET /appointments/mine`, returns a bare
array. The `{ <collection>: [], nextCursor: string|null }` envelope used by
the access log and payment history is therefore an assumption, and the
frontend treats `nextCursor: null` as "nothing older". If the backend lands
offset pagination instead, only the two services and their
`getNextPageParam` need to change.

### New routes from these three issues
- `/patient/appointments/:appointmentId` — nested under the patient
dashboard, because it is a detail screen of the hub.
- `/account/access-log`, `/payments`, `/payments/:intentId` — **not** nested
under `/patient`. `#34` names `/account/access-log` in the issue body and
payments are not patient-profile-scoped UI (a doctor or facility arriving
at `/payments` later would not belong under `/patient`), so these sit at
the literal paths the issues ask for, gated on the `patient` role in
`src/App.tsx` until a role actually needs them.
- All three lists are `useInfiniteQuery`-based; the patient asks for older
entries, the page never fetches ahead on its own.

## Roadmap / status
See `ROADMAP.md` in this repo, and Phase 7 in `docforum-core`'s
`ROADMAP.md` for the original (pre-split) frontend phase this was carved
Expand Down
85 changes: 83 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ real API" (no refresh-on-reload flow).
this repo's static frontend only.

## Phase W2 — Patient core flow
**Fully scoped into issues #1–#5, #18–#21, #26–#29.**
**Fully scoped into issues #1–#5, #18–#21, #26–#29, plus the follow-on patient issues #32–#34 and #37.**
- [ ] Doctor search (specialty + availability filter). Tracked as [issue #1](https://github.com/DocForum/docforum-web/issues/1) (Medium, 150 pts) — built against a documented assumed API shape, since `docforum-core` has no doctor-list endpoint yet.
- [ ] Booking + intake form. Tracked as [issue #2](https://github.com/DocForum/docforum-web/issues/2) (High, 200 pts) — must handle the `409` slot-taken response from `docforum-core`'s real booking-concurrency guarantee explicitly, not as a generic error.
- [ ] My appointments list. Tracked as [issue #3](https://github.com/DocForum/docforum-web/issues/3) (Trivial, 100 pts) — calls `docforum-core`'s real, already-implemented `GET /appointments/mine`, no assumption involved.
Expand All @@ -49,6 +49,15 @@ real API" (no refresh-on-reload flow).
- [x] Facility finder with a map and distance-sorted results. Tracked as [issue #29](https://github.com/DocForum/docforum-web/issues/29) (High, 200 pts) — assumed contract against `docforum-core` issue #74.


- [x] Patient home as a care hub: action-first dashboard ranking what needs
attention now. Tracked as [issue #33](https://github.com/DocForum/docforum-web/issues/33) (High, 200 pts) — no new backend contract; aggregates the
dependents and health-profile endpoints above. Its "Coming up" section is fed
by the appointment cards below, which read `docforum-core`'s real
`GET /appointments/mine`; a dedicated appointments list screen
([issue #3](https://github.com/DocForum/docforum-web/issues/3)) is still open.
- [x] Check-in and live queue position on appointment day. Tracked as [issue #32](https://github.com/DocForum/docforum-web/issues/32) (High, 200 pts) — the visit itself comes from `docforum-core`'s real `GET /appointments/mine`; the queue, check-in and its window are an assumed contract against `docforum-core` issue #129, which has not landed.
- [x] 'Who viewed my records' access log. Tracked as [issue #34](https://github.com/DocForum/docforum-web/issues/34) (High, 200 pts) — assumed contract against `docforum-core` issue #45 (PHI read-access log), which has not landed.
- [x] Payment history and receipts for patients. Tracked as [issue #37](https://github.com/DocForum/docforum-web/issues/37) (High, 200 pts) — `PaymentIntent` mirrors `docforum-core`'s real Phase 5.5 shape (`amountStroops`/`escrowId` arrive as strings and are formatted with BigInt, never through `Number`); the patient list/detail routes and the `refundDeadlineAt`/`facilityName` display fields are an assumed contract against `docforum-core` issue #90, which has not landed.
## Phase W3 — Doctor core flow
**Fully scoped into issues #6–#7, with #45–#49 added as doctor core-flow features.**
- [x] Today's appointments / intake view. Tracked as [issue #6](https://github.com/DocForum/docforum-web/issues/6) (Medium, 150 pts) — foundation laid via the doctor dashboard's live queue (`src/features/queue/`).
Expand Down Expand Up @@ -84,7 +93,7 @@ real API" (no refresh-on-reload flow).
transaction underneath. Admin accounts are seeded/invited, not
self-serve (PRD OQ-2, same as facility) — this page is real once such
a session exists, same caveat as the facility dashboard already had.
- [ ] Link transaction hashes/escrows to the Stellar testnet explorer. Tracked as [issue #10](https://github.com/DocForum/docforum-web/issues/10) (Trivial, 100 pts).
- [ ] Link transaction hashes/escrows to the Stellar testnet explorer. Tracked as [issue #10](https://github.com/DocForum/docforum-web/issues/10) (Trivial, 100 pts). The helper this needs now exists — `src/features/payments/explorer.ts`, used by the patient receipt built for [issue #37](https://github.com/DocForum/docforum-web/issues/37) under Phase W2 — but the admin console does not link out yet, so this stays open.

## Phase W6 — Admin console
Admin-only screens beyond payments. Everything goes through `docforum-core`'s admin API; admin accounts stay seeded/invited (PRD OQ-2).
Expand Down Expand Up @@ -355,6 +364,78 @@ Admin-only screens beyond payments. Everything goes through `docforum-core`'s ad
`/patient/facilities`). Strict privacy enforcement: transient in-memory
location only (never persisted to storage or URLs). 15 test files and
62/62 tests passing.
- 2026-10-16 — Rebuilt `PatientDashboardPage` from a placeholder into the
patient care hub ([issue #33](https://github.com/DocForum/docforum-web/issues/33)): `src/features/care-hub/` holds a pure
ranking module (`ranking.ts`) plus `useCareHub`, which fans out to the
existing dependents and health-profile queries in parallel and turns
their results into cards ranked `blocked now → needs you → good to know`,
with a 30-day "Recent" window. One failing source degrades its own
section instead of blanking the page, and a health section that *errored*
is never reported to the patient as an empty profile. Sections for
unbuilt features are hidden, not stubbed — "Coming up" only appears once
a card source for it lands, which [issue #32](https://github.com/DocForum/docforum-web/issues/32)
supplies from `docforum-core`'s real `GET /appointments/mine`. Adds
`src/features/care-hub/ranking.test.ts` and
`src/pages/PatientDashboardPage.test.tsx`; 57 tests pass (was 44 on
`main`), typecheck and build green.
- 2026-10-16 — Checked the three other assigned W2 issues against the
current code and backend: #32 (check-in + live queue position), #34
("who viewed my records" access log) and #37 (payment history and
receipts) each need a `docforum-core` endpoint that does not exist yet —
no check-in/queue route, no access-log route, and
`src/services/payments-service.ts` can create/read/fund a single
`PaymentIntent` by id but has no list-the-patient's-payments route.
Concluded they "stay unimplemented rather than built on a fabricated
contract", which was the wrong call and is corrected by the entries
below: hard rule 1 forbids calling Stellar/escrow directly, not building
a patient screen against a *documented* assumed shape — and #18, #19 and
#21 all did exactly that, recorded in "Known assumptions". What the check
did establish and what still gates these screens is the missing backend
work (`docforum-core` #129, #45 and #90).
- 2026-10-16 — Built #32: check-in and live queue position on appointment
day. `src/services/appointments-service.ts` calls `docforum-core`'s real
`GET /appointments/mine` (a bare array, newest-first, `availabilitySlot`
included) and the assumed queue/check-in routes. `src/features/appointments/`
holds the pure parts (`slot.ts` — local-calendar-day maths, so "today"
means the patient's day not UTC's; `queue.ts` — notification threshold and
a signature so a patient is told about a *change*, not every poll), the
`useAppointmentQueue` hook (polls only while the visit is on screen and
`status === 'scheduled'`, `gcTime: 0` so leaving the page stops the
requests) and `QueuePanel`. `AppointmentDetailPage` at
`/patient/appointments/:appointmentId` is reachable from the care hub's
"Coming up"/"Action needed" cards; check-in UI only appears on the day of
the visit. Estimates are always labelled as estimates, `peopleAhead: null`
renders as "not published" rather than "zero people ahead", and a 404 from
the unlanded queue route reads "check-in is not available for this visit
yet" instead of an empty queue. `src/hooks/useBrowserNotification.ts` asks
for permission only on a click and never re-asks after a refusal.
- 2026-10-16 — Built #34: 'who viewed my records' access log at
`/account/access-log`. `src/services/access-log-service.ts` reads the
assumed `GET /patients/me/access-log` (cursor-paginated, `from`/`to`/
`recordType` filters) through `useInfiniteQuery`, so "show older entries"
asks for the next cursor only when the patient asks for it.
`src/features/account/` renders who (name + role), what and why, and
never an internal id; the "why" copy explains the treating-clinician and
fulfilling-facility cases rather than dumping a reason code. The
report-a-concern link is only rendered when `VITE_SUPPORT_EMAIL` is set —
an invented mailto would send a patient's privacy complaint nowhere.
- 2026-10-16 — Built #37: payment history and printable receipts at
`/payments` and `/payments/:intentId`. `src/features/payments/stroops.ts`
converts `amountStroops` with BigInt only, so an amount above 2^53 prints
exactly (`900,719,925.4740993 XLM`) instead of silently rounding the
patient's own money; `explorer.ts` is the single source of testnet
explorer links (all calls still go through `docforum-core` — hard rule 1
— this only turns a hash core already returned into a URL); `status.ts`
labels and explains every `PaymentIntentStatus` in the patient's terms.
`PaymentReceipt` keeps the transaction hash and escrow contract id as
readable text so a printed copy is still verifiable, hides the page
chrome at print time, and omits `orderId`, which a patient cannot act on.
The assumed `refundDeadlineAt` renders nothing when absent rather than a
guessed window.
- 2026-10-16 — #32/#34/#37 together: 156 tests pass across 25 files (57 on
this branch before, 44 on `main`), `npm run typecheck` clean, `npm run
build` clean. Types for every assumed shape are annotated as assumed in
`src/types/models.ts` and recorded in `ARCHITECTURE_ESSENTIALS.md`.
- 2026-10-17 — Built Phase W3 doctor core flow (#45–#49): live queue with
start/complete visits, running delay + manual override and 4s undo window
(#49); doctor messages inbox with unread badge, truncated PHI previews,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading