feat: task 1.8 logout flow + cross-tab sync + strict tsconfig

- src/auth/logout.ts: performLogout({ queryClient, navigate? })
  orchestration. Calls store.logout(), clears query cache, navigates
  to /login. Server-call failures swallowed; local state still flips
  to anonymous.
- src/auth/cross-tab-sync.ts: startCrossTabSync(). Subscribes to the
  auth store and bumps trm-auth-version in localStorage on status
  transitions; listens for storage events and re-runs initialize()
  when another tab bumps. Idempotent via module-level guard. No-ops
  if localStorage is unavailable.
- src/ui/components/logout-button.tsx: <LogoutButton> wraps shadcn
  Button. Pass-through props but locks onClick / disabled / children.
  Local isLoggingOut for "Signing out..." indicator + double-click
  guard.
- src/auth/bootstrap.tsx: <AuthBootstrap> now calls startCrossTabSync()
  alongside the existing init steps. Single mount point.
- src/auth/index.ts: re-export performLogout + startCrossTabSync.
- src/routes/_authed/index.tsx: replaced the inline sign-out button
  with <LogoutButton />. The existing useEffect on 'anonymous' status
  handles navigation when cross-tab sync fires.

Bonus: tsconfig.app.json now has "strict": true. TanStack Router emits
a conditional-type error ("strictNullChecks must be enabled") in
editors when strict is off; tsc -b was lenient. Caught in 1.7's
review. Typecheck remained green after enabling.
This commit is contained in:
2026-05-02 18:24:38 +02:00
parent 8e38f69205
commit d3ccdded23
9 changed files with 137 additions and 15 deletions
+17 -1
View File
@@ -131,4 +131,20 @@ This is the right trade-off: logout that fails should _not_ leave the user appea
## Done
(Filled in when the task lands.)
- **`src/auth/logout.ts`** — `performLogout({ queryClient, navigate? })` orchestration helper. Calls `useAuthStore.getState().logout()` (which swallows server-call failures and forces local state to anonymous), then `queryClient.clear()` (so the next user doesn't see cached data), then navigates to `/login` if a navigate function was passed.
- **`src/auth/cross-tab-sync.ts`** — `startCrossTabSync()`. Subscribes to the auth store and bumps a `trm-auth-version` localStorage key on status transitions. Listens for storage events and re-runs `initialize()` when another tab bumps the key. Idempotent (HMR-safe) via a module-level `started` flag. Gracefully no-ops if `localStorage` is unavailable (private browsing, quota).
- **`src/ui/components/logout-button.tsx`** — `<LogoutButton>` component wraps the shadcn `Button`. Pass-through props (variant, size, etc.) but locks `onClick` / `disabled` / `children` so callers can't override the orchestration. Local `isLoggingOut` state shows "Signing out…" while the call is in flight; double-click guard.
- **`src/auth/bootstrap.tsx`** — `<AuthBootstrap>` updated to call `startCrossTabSync()` alongside `initDirectusClient()` and `initialize()`. Single mount point for all auth-side wiring.
- **`src/auth/index.ts`** — re-exports `performLogout` and `startCrossTabSync`. Alphabetised the exports.
- **`src/routes/_authed/index.tsx`** — replaced the inline button with `<LogoutButton />`. The page's existing `useEffect` on the `'anonymous'` status transition still handles the navigation when the cross-tab sync fires (no separate code path needed).
**Bonus from this task** — caught a missing `"strict": true` in `tsconfig.app.json`. TanStack Router emits a `"strictNullChecks must be enabled"` conditional-type error in editors when strict mode is off; CI's `tsc -b` was lenient. Added `strict: true` (along with the existing strict-adjacent flags); typecheck still green.
**Smoke check:** `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm build` all green. Bundle: 353KB main + same per-route chunks as 1.7 (login 37KB, _authed 1.4KB grew slightly with the LogoutButton). No measurable bundle impact from the logout orchestration.
**Browser smoke pending:**
1. Sign in. Click "Sign out". Watch button show "Signing out…", then redirect to `/login`. Hard refresh on `/login` stays on `/login`.
2. Sign in in tab A. Open tab B (same SPA). Sign out in tab A. Tab B's home page should redirect to `/login` on next interaction (or immediately, depending on whether the storage event triggers re-render).
3. With DevTools "Network: offline", click sign out. Should still navigate to `/login` (server call fails, local state forced to anonymous).
Landed in `PENDING_SHA`.