add refinement phases
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# 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 `customer` role, role hydration silently falls back to `customer`, 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](refinement-phase-0-bring-up.md), [Phase 1](refinement-phase-1-database-and-seed.md) ·
|
||||
> **Unlocks:** reaching the nurse & admin experiences at all
|
||||
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../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):
|
||||
|
||||
1. **A fresh phone login has only the `customer` role.** `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` / `/admin` if `/me.roles`
|
||||
actually contains `nurse`/`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.
|
||||
2. **A hard-coded `customer` fallback 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 if `useSessionRoleSync()` hasn't
|
||||
hydrated yet, or `/me` errored (backend down), **every private screen resolves to the customer shell** — a
|
||||
nurse can be silently shown the customer app.
|
||||
3. **No client-side role guard on the shells.** `(customer)`, `/nurse`, `/admin`, `/partner` layouts 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).
|
||||
4. **Roles depend entirely on a successful client `/me`.** The server seed
|
||||
(`client/src/lib/auth/server.ts`) only sets `isAuthenticated` (the JWE is opaque server-side); roles come
|
||||
only from the client `/me` call in `useSessionRoleSync`. 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 the `useAdminCapabilities` roleCodes gating),
|
||||
`client/src/constants/roles.ts` (`DEFAULT_ROLE`).
|
||||
- `client/src/app/[locale]/(private-routes)/layout.tsx` (mounts `useSessionRoleSync`) and the four shell
|
||||
layouts under it (`(customer)`, `nurse`, `admin`, `partner`).
|
||||
- Server side: `Controllers/V1/MeController` (`/me`, `select_role`) and how `MeResult` exposes `roles` +
|
||||
`roleCodes`; the role vocabulary in `Domain/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) → `/me` now returns `["customer","nurse"]` → `resolveRoleDestination`
|
||||
routes to `/nurse`. Prove this end-to-end against the real backend. (The [Phase 1](refinement-phase-1-database-and-seed.md)
|
||||
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_role` 403s for them, by design). Provide a
|
||||
path to an admin session: either the Phase 1 seed grants a demo phone user the `admin` role, or (better,
|
||||
and it's on the roadmap anyway) deliver REQ-031's `admin_roles/grant_role` so an existing admin can grant
|
||||
staff roles from `/admin/roles`. Pick one and document it. Confirm `/me` returns the fine-grained
|
||||
`roleCodes` the admin console's `useAdminCapabilities()` 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 `/partner` resolves 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 `useMe` is 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 to `DEFAULT_ROLE` once `/me` has actually resolved with an empty/customer role set.
|
||||
- If `/me` **fails** (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 `resolveRoleDestination` the 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 → `/nurse`
|
||||
when they hold `nurse`; a pure customer hitting `/nurse` is 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 `/me` fails). Gate on a resolved-vs-pending distinction.
|
||||
- **Admin roles are server-granted, never self-selected.** `select_role` must keep 403-ing for staff roles;
|
||||
reach admin via seed or RBAC grant, not by loosening `select_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](../../phases/_shared/definition-of-done.md):
|
||||
|
||||
- [ ] `npm run check` green; new shared components have `*.test.tsx`; `en.json`/`fa.json` in 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 `/nurse` after 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 `/nurse` when `/me` succeeds.
|
||||
|
||||
## 7. How to test (what a human can verify after this phase)
|
||||
|
||||
1. Log in as the seeded **verified nurse** phone account → land on `/nurse`, see the nurse shell + dashboard.
|
||||
2. Log in as a fresh customer → land on `/` (customer app). Tap "become a nurse" (SelectRole) → confirm
|
||||
`POST /me/select_role` in the Network tab → after `/me` refetch you're routed to `/nurse`.
|
||||
3. Log in as the seeded **admin** → land on `/admin`; confirm the sidebar shows only the consoles your
|
||||
`roleCodes` allow (`useAdminCapabilities`).
|
||||
4. As a pure customer, manually visit `/nurse` → redirected home with a toast.
|
||||
5. 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 `customer` fallback, now fixed.
|
||||
Reference in New Issue
Block a user