Appearance
API Communication
The dashboard talks to the Flask API over HTTPS with the browser's HttpOnly session cookie. There is no token in the bundle, no Supabase data access from the browser, and no server-side rendering.
Request conventions
- Base URL:
VITE_DPS_API_BASE_URLwhen set (production:https://api.eccdps.org), otherwise/api(the Vite dev proxy rewrites/api→ the configuredDPS_API_BASE_URL). - Credentials: every request sends
credentials: "include"— the session cookie rides along. - Content:
Accept: application/jsonalways;Content-Type: application/jsonwhen there is a body. - Typed wrappers: pages never call
fetchdirectly. They import fromsrc/lib/api/*— e.g.listReportsPage,getReport,reportAction,addNote,listQueueActions,listPunishments,getCurrentAgent. Each wrapper encodes the endpoint, method, params, and response type (src/lib/api/types.ts).
Authentication state
AuthProvidercallsgetCurrentAgent()(GET /auth/me) on mount and exposesagent,permissions,canHandleSupervisor,admin,can(),canAdmin(),refresh(), andsignOut().- A 401 during session restore simply leaves the user signed out (redirected to
/loginbyProtectedRoutes); other failures are logged. signInWithDiscord()is a full-page navigation to/auth/discord/login— the OAuth flow runs server-side and redirects back.signOut()callsPOST /auth/logoutthen clears local state.
Error handling
apiFetch(inclient.ts) converts non-2xx responses intoApiError(with the server'serrormessage and status); network failures becomeApiErrorwith status 0 ("Could not reach the DPS API…").- Pages catch
ApiErrorand renderErrorStatewith the message and a retry action where appropriate. - Mutation failures are surfaced via
ToastProvider(error toasts), and specific conflict statuses (409s) are usually worth re-fetching the resource because another actor may have changed it.
Loading states
- Page-level fetches render
LoadingStatefromcomponents/DataState.tsxwhile in flight. - The auth gate renders a "Loading secure session…" screen until
/auth/meresolves. - Inline mutations disable the triggering control while pending and restore it on failure (per-page pattern).
Mutation patterns
- Build the typed payload (e.g.
{ action: "validate", reason }). - Call the wrapper (e.g.
reportAction(id, "validate", reason)). - On success: update local state from the returned payload (most report mutations return the full updated
report) and/or re-fetch the resource; show a success toast. - On
ApiError: show an error toast / inline error; re-enable controls; re-fetch the resource when the error was a conflict.
The frontend never performs mutations optimistically against authoritative state. Server responses are the source of truth; Realtime (for training) only triggers authoritative re-fetches.
Development proxy
vite.config.ts proxies /api to DPS_API_BASE_URL (optionally adding DPS_API_KEY as the Authorization header for local bot-path testing). See Local setup for the exact commands.