# 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 ``; 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`.