9.8 KiB
Refinement Phase 2 — Auth & role-aware navigation (the "only customer side" fix)
Mission: fix the most visible symptom — "there are nurse and admin pages, but running the frontend only ever shows the customer side." Auth is already real; the reason you only see the customer app is that a fresh session has only the
customerrole, role hydration silently falls back tocustomer, and nothing routes a nurse/admin to their shell. This phase makes login, role hydration, and role-aware navigation work end-to-end for all three actors.Track: integration (mostly frontend + a little backend) · Depends on: Phase 0, Phase 1 · Unlocks: reaching the nurse & admin experiences at all Before you start, read ../../phases/_shared/agent-operating-rules.md.
1. Context — where this sits
Auth is the one domain already wired to the real backend (USE_AUTH_MOCK = false). The phone-OTP flow,
session cookies, silent refresh, and /me hydration all work. So why is only the customer app visible?
Diagnosed root causes (all verified in code):
- A fresh phone login has only the
customerrole.resolveRoleDestination(client/src/services/auth/routing.ts) sends a customer to/(the customer app lives at the app root — the(customer)route group has no URL segment) and only sends to/nurse//adminif/me.rolesactually containsnurse/admin. A brand-new user holds neither until they self-select the nurse role (POST /api/v1/me/select_role) or an admin grants a staff role. Nothing in the current run does that, so everyone lands on the customer app. - A hard-coded
customerfallback masks un-hydrated / failed role state.client/src/constants/roles.ts→DEFAULT_ROLE = customer;useActorRole()(client/src/hooks/auth.ts:29) returns it whenever roles are empty. So ifuseSessionRoleSync()hasn't hydrated yet, or/meerrored (backend down), every private screen resolves to the customer shell — a nurse can be silently shown the customer app. - No client-side role guard on the shells.
(customer),/nurse,/admin,/partnerlayouts each just wrap their shell; anyone can navigate to any of them (the server still enforces per-endpoint auth, but the navigation/chrome isn't role-aware, so the experience is confusing). - Roles depend entirely on a successful client
/me. The server seed (client/src/lib/auth/server.ts) only setsisAuthenticated(the JWE is opaque server-side); roles come only from the client/mecall inuseSessionRoleSync. If that call fails, roles never populate.
What already exists (do not rebuild): resolveRoleDestination, RoleRouter, SelectRole,
useSessionRoleSync, useActorRole, the three shells, and POST me/select_role. This phase makes them
robust and reachable — it does not rebuild the auth flow.
2. Required reading (do this first)
client/src/services/auth/routing.ts(resolveRoleDestination),client/src/components/auth/RoleRouter.tsx,client/src/components/auth/SelectRole.tsx.client/src/services/auth/hooks/useSessionRoleSync.ts,client/src/services/auth/hooks/useMe.ts.client/src/hooks/auth.ts(useActorRole, and theuseAdminCapabilitiesroleCodes gating),client/src/constants/roles.ts(DEFAULT_ROLE).client/src/app/[locale]/(private-routes)/layout.tsx(mountsuseSessionRoleSync) and the four shell layouts under it ((customer),nurse,admin,partner).- Server side:
Controllers/V1/MeController(/me,select_role) and howMeResultexposesroles+roleCodes; the role vocabulary inDomain/Entities/User/RoleNames. - REQ-004 in
dev/shared-working-context/frontend/requests/for-backend.md(the "client owns the active-role choice" decision to confirm) and REQ-031 (RBAC grant endpoints — needed if you want to grant staff roles from the UI rather than by seed).
3. Scope — build this
3.1 Make all three actor sessions reachable in development
- Nurse: verify the real
POST /api/v1/me/select_role { role: "nurse" }path — a customer taps "become a nurse" (the B1 switch / SelectRole) →/menow returns["customer","nurse"]→resolveRoleDestinationroutes to/nurse. Prove this end-to-end against the real backend. (The Phase 1 demo seed can also pre-make nurse-roled phone accounts so you can log straight into/nurse.) - Admin: admin sub-roles are not self-selectable (
select_role403s for them, by design). Provide a path to an admin session: either the Phase 1 seed grants a demo phone user theadminrole, or (better, and it's on the roadmap anyway) deliver REQ-031'sadmin_roles/grant_roleso an existing admin can grant staff roles from/admin/roles. Pick one and document it. Confirm/mereturns the fine-grainedroleCodesthe admin console'suseAdminCapabilities()gates on (e.g.moderation,finance). - Partner: a partner-center admin is a separate authz scope (
useMyPartnerCenter). Ensure a demo user is associated with a seeded partner center (Phase 1) so/partnerresolves instead of access-denied.
3.2 Harden role hydration so it never silently mis-shells
- Distinguish "roles not yet loaded" from "user has no nurse/admin role." While
useMeis in-flight on a private route, render a neutral loading state (not the customer shell) so a nurse is never flashed the wrong app. Only fall back toDEFAULT_ROLEonce/mehas actually resolved with an empty/customer role set. - If
/mefails (backend down), surface an explicit "couldn't load your account" state rather than silently defaulting to customer — otherwise a transient error downgrades a nurse/admin to the customer app. - Keep
resolveRoleDestinationthe single source of truth for "which app" — don't scatter role checks.
3.3 Role-aware navigation guards (chrome, not security)
- Add a lightweight guard so navigating to a shell the user lacks the role for redirects (nurse →
/nursewhen they holdnurse; a pure customer hitting/nurseis redirected home with a toast). The server remains the security boundary; this is UX so the actor experience matches the session. - For a dual-role user, honor the client-owned intended role (the A1 vs B1 login switch) per REQ-004's "client owns it" decision, and let them switch actor from the shell.
3.4 Backend confirmations / small adds (coordinate with Phase 3)
- REQ-004 (zero code): write the "client owns the active-role choice" decision into the tracker so the router behavior is contract-blessed.
- If you chose the RBAC route in 3.1: REQ-031
admin_roles/list|grant|revoke(this is the one net-new backend surface this phase may need; otherwise it's a Phase 1 seed).
4. Mocks & seams in this phase
None. Auth is already real. Do not reach for USE_AUTH_MOCK = true to demo roles — that mock's
MOCK_SCENARIO toggle is an offline convenience, and using it here would hide the real hydration bug this
phase exists to fix. The whole point is that real /me roles drive navigation.
5. Critical rules you must not get wrong
- Never treat "roles still loading" as "customer." That conflation is the core bug — a nurse gets the
customer app for a beat (or forever, if
/mefails). Gate on a resolved-vs-pending distinction. - Admin roles are server-granted, never self-selected.
select_rolemust keep 403-ing for staff roles; reach admin via seed or RBAC grant, not by looseningselect_role. - Client guards are UX, not security. Keep every server endpoint's authorization intact; the guard only redirects the browser to the right shell.
- i18n both locales for any new copy (loading/mis-role/redirect states).
6. Definition of Done
On top of the shared definition-of-done.md:
npm run checkgreen; new shared components have*.test.tsx;en.json/fa.jsonin sync.- Logging in as a nurse-roled phone user lands on
/nurse; an admin on/admin; a partner admin on/partner; a plain customer on/. - A customer who selects the nurse role (
select_role) is routed to/nurseafter the next/me. - Navigating to a shell you lack redirects (with a toast), not a broken page.
- With the backend momentarily stopped, a logged-in nurse sees a loading/error state — not the
customer app — and recovers to
/nursewhen/mesucceeds.
7. How to test (what a human can verify after this phase)
- Log in as the seeded verified nurse phone account → land on
/nurse, see the nurse shell + dashboard. - Log in as a fresh customer → land on
/(customer app). Tap "become a nurse" (SelectRole) → confirmPOST /me/select_rolein the Network tab → after/merefetch you're routed to/nurse. - Log in as the seeded admin → land on
/admin; confirm the sidebar shows only the consoles yourroleCodesallow (useAdminCapabilities). - As a pure customer, manually visit
/nurse→ redirected home with a toast. - Stop the API, reload a nurse session → loading/error state (not the customer app); restart → recovers.
8. Hand off & document (close the phase)
- Update
client/CLAUDE.md(the auth/role section) to describe the resolved-vs-pending hydration rule and the role-aware redirects; update the "Project Structure" note if you add a guard component. - Answer REQ-004 in the tracker; if you built RBAC grant, mark REQ-031 delivered.
- Report the exact demo credentials for each actor and the role-reachability path. Save a memory note that the
"only customer side" symptom was role-reachability + silent
customerfallback, now fixed.