# Hardening Phase 3 — Session & error-surface hardening

> Close the shared-device data leak (logout leaves every domain's cache warm for the next login), turn
> the raw stack-trace error boundary into a branded recovery surface with route-level error pages, and
> localize the four hardcoded-English failure toasts. Plus: client-side capability gating for the four
> admin consoles that render without a check.
> **Track:** frontend · **Depends on:** Phase 0 · **Unlocks:** — (independent polish, safe anytime after 0)
> **Before you start, read [_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md).**

## 1. Context — where this sits

Fixes **H-11 … H-14** from [issues.md](issues.md). All four are verified, none is touched by any other
phase, and none needs backend work.

**What already exists (do not rebuild):** `useLogout` as the single logout path, the
`AuthAccountError` recovery-card pattern (mirror it for the boundary), `dispatchToast`/`ToastBridge`
(the non-React toast bridge — keep it, localize what flows through it), `useAdminCapabilities()`
(`src/hooks/capabilities.ts`) and the non-leaking access-denied patterns used elsewhere in the admin
screens.

## 2. Required reading (do this first)

- [issues.md](issues.md) H-11/H-12/H-13/H-14 — evidence; don't re-audit.
- `client/src/services/auth/hooks/{useLogout,useVerifyOtp,useSelectRole}.ts`,
  `client/src/lib/query/{queryClient.ts,QueryProvider.tsx}`,
  `client/src/components/common/ErrorBoundary.tsx` + its two consumers,
  `client/src/lib/api/client.ts` + `client/src/lib/toast/*`,
  the four admin pages (`admin/{audit,verification,tickets,roles}/page.tsx`) + `AdminLayout.tsx`.
- `client/CLAUDE.md` → error contract for `clientFetch`, i18n rules, unit-testing rule.
- Next.js `error.tsx` / `global-error.tsx` file conventions.

## 3. Scope — build this

1. **H-11 — cache isolation across sessions.** `useLogout.onSettled`: `queryClient.clear()` (replace
   the auth-only `removeQueries`). Symmetrically clear/reset before seeding the new session in
   `useVerifyOtp.onSuccess` and `useSelectRole.onSuccess`, so User B never sees User A's cached
   patients/bookings/addresses/tickets/notifications on a shared device. Check nothing depends on
   surviving cache entries across logout (geo/catalog reference data may simply refetch — correct).
2. **H-12 — real error surfaces.**
   - Rebuild `ErrorBoundary`'s fallback as a branded, i18n'd recovery card (mirror
     `AuthAccountError`): translated generic message + retry (re-render children / reload), raw
     `error.toString()` + component stack rendered **only** in development. Keep the class component;
     add the error-reporting hook point (a single function you can later wire to a service — no TODO
     comment left behind).
   - Add `src/app/[locale]/error.tsx` and `global-error.tsx` so errors thrown above the shells
     (RoleGuard, providers, layouts) get the same branded treatment instead of Next's default. Respect
     the root-layout constraint (`global-error` must render its own `<html>`; keep locale/dir sane).
3. **H-13 — localize the failure toasts.** `lib/api/client.ts:57,80,86,91`: replace the four English
   literals with a small locale-keyed dictionary module (the fetch layer already knows the locale;
   it can't call `useTranslations`). Add keys (suggest an `errors` namespace) to **both**
   `messages/en.json` and `messages/fa.json`. Keep the error contract exactly (which statuses toast,
   which throw) — only the copy source changes.
4. **H-14 — capability-gate the four admin consoles.** Add a shared `CapabilityGuard`
   (`need: keyof AdminCapabilities`) that early-returns the existing non-leaking access-denied state,
   and wrap the page bodies of `admin/audit` (canViewAudit), `admin/verification` (canVerify),
   `admin/tickets` (canManageTickets), `admin/roles` (canManageRoles) — before their data hooks fire.
   Display convenience, not security (the server enforces after Phase 1) — but a scoped admin
   deep-linking must see the denied card, not the data.

## 4. Mocks & seams in this phase

None.

## 5. Critical rules you must not get wrong

- `queryClient.clear()` on logout must run **after** the server revoke call is issued (keep the
  existing revoke → clear-tokens → dispatch → redirect ordering; the cache clear joins it, doesn't
  reorder it).
- Don't toast inside hooks for 401/403/5xx (already toasted by `clientFetch`) — unchanged rule.
- `global-error.tsx` replaces the root layout when it triggers — it must be self-contained (no
  providers assumed), bilingual-safe, and never crash itself.
- Every new user-visible string in both message files; shared components (`CapabilityGuard`, the new
  boundary fallback if extracted) get co-located tests per the client testing rule.
- Do not turn the boundary into a swallow-everything — rethrow/log in dev so DX doesn't regress.

## 6. Definition of Done

- Shared-device test: login A (customer with patients) → logout → login B → B never sees A's data
  (verify React Query devtools cache is empty right after login).
- Throw a test error inside a page in dev → branded Persian card with retry; stack visible in dev
  build only. Route-level error files exist and render.
- On `fa`, kill the API and trigger a fetch → Persian network-error toast; expire the session → Persian
  session-expired toast + login redirect (Phase 0 behavior).
- Finance-scoped admin deep-links `/admin/audit` → access-denied card, no data fetch fired.
- `npm run check` + `npm run test:ci` green (new tests for CapabilityGuard + boundary fallback).

## 7. How to test (human)

1. Two seeded accounts on one browser: full logout/login swap watching the patients screen.
2. Temporarily `throw new Error('boom')` in a page render; check both dev and `npm run build && start`.
3. Stop the API mid-session on `/fa`; watch toast language; restart, expire tokens, watch the redirect.
4. Login as 09120000021 (finance), deep-link the four consoles.

## 8. Hand off & document

- Tick H-11…H-14 in [issues.md](issues.md) with commit hashes.
- Update `client/CLAUDE.md` (error contract copy source, error.tsx files in the structure tree,
  logout lifecycle).
- Write `dev/shared-working-context/reports/hardening-phase-3-report.md`.
