Foundations for the sprint 1 backend wiring. No UI behavior change beyond
the loading state in AppShell, but everything below the wire is now real:
- vite.config.ts adds `server.proxy['/api']` → http://localhost:5000
(overridable via VITE_DEV_API_TARGET). In prod Caddy routes /api → backend
on the same origin, so the same `/api/v1/...` paths work without changes.
- src/types/api.ts hand-rolled against the backend Pydantic schemas.
User / Engagement / EngagementCreate / Login / ApiError / ApiValidationError.
Should be regenerated from OpenAPI once backend exposes it.
- src/lib/api.ts: thin fetch wrapper. Always credentials:'include' so the
HttpOnly session cookie travels. 4xx/5xx normalize into ApiClientError
with typed `body` (ApiError | ApiValidationError | null). No retry loop —
that's TanStack Query's policy.
- src/session/sessionApi.ts: 1:1 functions for /auth/me, /auth/login,
/auth/logout. fetchMe maps 401 → null so "unauthenticated" is data, not
an error.
- src/session/useSession.ts: now a TanStack Query hook against
SESSION_QUERY_KEY (`['session']`). Returns { user, isLoading, isError,
signOut, isSigningOut }. Cookie is the source of truth, server is the
resolver, query is the cache.
- Drop sessionStorage mock layer entirely: src/mocks/session.ts,
src/session/SessionContext.{tsx,context.ts}, src/routing/Root.tsx all
removed. No more provider tree — QueryClientProvider in App.tsx is the
only global state container.
- AppShell renders a "resolving session" state during /auth/me's first
flight so users with a valid cookie don't see a /login flash on direct
navigation to a protected URL.
- StatusRail gains an optional `sessionState="resolving"` slot used by
the loading shell.
- Sidebar's Sign-out wires POST /auth/logout, invalidates the session
cache, and always navigates to /login regardless of the call outcome
(a failed logout still expires the local cache so users aren't stuck
on a broken cookie).
- types/roles.ts loses SessionUser (replaced by api.ts User which is the
authoritative shape).