Nurse Portal — Clinical Workflow and Vitals
Audiences: clinical-buyer, developer
Pre-clinic and intra-clinic surface for nurses: today's dashboard, waiting room triage, vitals capture, patient list, intake forms, AI/voice assists, and secure messenger — gated to
NURSE_ONLY.
Business Purpose
The nurse portal is where the patient is prepared for the doctor. Before the clinician sees the patient, the nurse has called them from the waiting room, taken vitals, reviewed the intake form, and updated the chart. Each of those interactions used to be paper-and-clipboard; the nurse portal converts them into structured data that flows directly into the encounter the doctor opens five minutes later.
Without the nurse portal, that prep work either does not happen (the doctor pays the time tax) or it happens but stays on paper (the data is lost). With it, vitals are captured in the same FHIR encounter the doctor will open, waiting-room status updates move appointments forward, and the clinic's throughput grows without adding clinicians.
Audiences
- Clinical buyer (clinic admin): nurse productivity surface — direct waiting-room control, vitals at the same record as the visit, and intake handoff that closes the loop with the receptionist (check-in) and the doctor (encounter open).
- Doctor: the source of pre-encounter prep — vitals and intake answers are already attached to the chart by the time the doctor enters the room.
- Developer/partner: explains the nurse routes, the
NURSE_ONLYgate, and which pages are currently placeholders ("coming soon") vs. fully implemented. - Internal (ops/support): where vitals land, how the waiting room state machine drives status changes, and how nurse-to-doctor handoff is recorded.
Architecture
Routes
portal/nurse/ ├── layout.tsx → createRoleLayout(NURSE_CONFIG, NurseNav) ├── page.tsx → root entry ├── dashboard/ → today's KPIs + appointment count ├── waiting-room/ → triage list + status transitions ├── patients/ → roster ├── vitals/ → vitals capture (placeholder — "coming soon") ├── notes/ → nurse notes ├── ai/ → AI tooling entry (medical-tool-aware) ├── flowagent/ → conversational intake/flow ├── forms/ → fillable forms inbox ├── messages/, messenger/ → secure messaging ├── help/ → help center ├── hr/ → HR/admin ├── vivoxx/ → voice/LiveKit surface for nurse-side dictation ├── profile/ → identity + preferences └── (error, loading, not-found) → standard route helpers
Layout composition
ddx-web/src/app/[locale]/portal/nurse/layout.tsx:13-17:
function NurseNav() { return <PortalNavigation roleKey="nurse" />; }
export default createRoleLayout(NURSE_CONFIG, NurseNav);
NURSE_CONFIG (ddx-web/src/lib/portal/role-configs.ts:41-51):
authRequirement: 'NURSE_ONLY', roleBadge.icon: HeartbeatIcon, variant: 'success',
brandNameKey: 'portals.nurse'. No taglineFormatter, no needsBackendStatus,
no extraElements. The simplest of the clinical-portal configs.
Authorization shape (nuance)
- Layout-level gate (
NURSE_ONLY) runs viacreateRoleLayout→ no non-nurse can reach anyportal/nurse/*page. - Per-page gating varies: some pages call
requireAuth(locale)only (e.g.dashboard/page.tsx:18,vitals/page.tsx:19), relying on the layout for role enforcement. The defense-in-depth pattern used in doctor + patient portals (requireRoleAccess('XYZ_ONLY', locale)at the page) is not consistently applied in the nurse portal as of the audit date. Layout enforcement is sufficient as long as every page is reached via the standard routing path — but a copy-pasted handler that uses an explicit role string would catch a misconfigured layout in audit.
Placeholder pages
Several nurse surfaces are not yet implemented and render the messages.comingSoon
common key:
vitals/page.tsx:24-27— renders<PageContainer>with the "coming soon" body.
These are intentional placeholders; the route exists so the navigation entry resolves and i18n keys are exercised.
Tech Stack & Choices
| Layer | Choice | Why |
|---|---|---|
| Layout factory | createRoleLayout(NURSE_CONFIG, NurseNav) | Symmetric with other portals; one factory governs all. |
| Navigation | PortalNavigation roleKey="nurse" | Generic shared component; nurse-specific sections come from nav-configs. |
| Role gate | NURSE_ONLY at the layout | Backstopped by backend RBAC (always authoritative). |
| Brand icon | HeartbeatIcon (success variant) | Reflects vitals + monitoring focus; distinct from doctor's HeartIcon primary. |
| i18n namespace | nurse | One namespace per role; sub-namespaces (nurse.dashboard, nurse.waitingRoom) keep keys narrow. |
Rejected: a custom nurse-specific layout (would diverge from the eleven-portal
symmetry), per-page requireRoleAccess('NURSE_ONLY', locale) on every page (the
layout already enforces; redundant calls add overhead).
Data Flow
- Nurse logs in → JWT carries
role: NURSE. - Browser hits
/<locale>/portal/nurse/dashboard. proxy.ts:248-319— locale prefix + session gate.portal/nurse/layout.tsx:17runscreateRoleLayout(NURSE_CONFIG, NurseNav):requireRoleAccess('NURSE_ONLY', locale)returns the nurse user, organization is fetched,<PortalShell>mounts withNurseNavsidebar.dashboard/page.tsx:16-29awaitsparams, callsrequireAuth(locale), renders<NurseDashboardClient locale={locale} userName={user.name} todayAppointmentsCount={0}>. The count currently displays0(placeholder pending live data wiring from the appointments service).- Waiting room (
waiting-room/page.tsx, 170 lines) — server-side fetch + client triage interactions, status transitions through the BFF. - Vitals capture flows into the FHIR encounter on the same patient the doctor opens next. (Wiring covered under the W2 medical-cards topic and the doctor-portal command center.)
Implicated Code
ddx-web/src/app/[locale]/portal/nurse/layout.tsx:1-17—createRoleLayoutinvocation withNurseNavwrapper aroundPortalNavigation.ddx-web/src/lib/portal/role-configs.ts:41-51—NURSE_CONFIGdefinition.ddx-web/src/app/[locale]/portal/nurse/dashboard/page.tsx:16-29— server-siderequireAuth+ delegation toNurseDashboardClient.ddx-web/src/app/[locale]/portal/nurse/vitals/page.tsx:17-28— placeholder "coming soon" page usingmessages.comingSooncommon key.ddx-web/src/lib/api/rbac-config.ts:36—NURSE: NURSE_PERMISSIONSregistration.ddx-web/src/app/[locale]/portal/nurse/waiting-room/page.tsx(170 lines) — full waiting-room triage server component.
Operational Notes
- Layout-level role gate —
NURSE_CONFIG.authRequirement = 'NURSE_ONLY'is the single point of role enforcement for most nurse pages. Removing or weakening this gate exposes every child page; never edit the auth requirement without the security review. - Placeholder pages render
messages.comingSoon— when a stakeholder reports "vitals page is empty", confirm againstvitals/page.tsx:24-27before assuming a regression — it's intentional until vitals wiring lands. - Defense-in-depth audit (improvement opportunity) — adding
requireRoleAccess('NURSE_ONLY', locale)per page (in addition to the layout gate) would mirror the doctor/patient pattern and is an easy hardening win. PortalNavigation roleKey="nurse"— the nurse-side nav resolves throughgetNavConfigByRole('nurse')inlib/portal/nav-configs. Adding a new nurse surface requires both a route and a nav-config entry (withnurse.*i18n keys in all three locales).- No
(with-nav)/group — the nurse portal has no chrome-free siblings, so the layout is a single role factory call — simpler than the doctor portal.
Related Topics
- Multi-Portal Architecture — eleven-portal shell.
- i18n — Multi-Lingual Interface —
nurse.*namespace. - Doctor Portal — receives nurse's pre-encounter prep (vitals, intake).
- Receptionist Portal — upstream surface (check-in feeds the waiting room the nurse triages).