Appearance
Application Structure
The dashboard is a React 19 + TypeScript + Vite single-page application (ecc-dps-dashboard/), styled with Tailwind CSS 4 and the shadcn/base-ui component model. The same bundle serves both the normal DPS dashboard and the Training Center; the app chooses which to boot from the hostname at load time.
Entry point and app split
src/main.tsximports global CSS and mountsAppinStrictMode.src/App.tsx:- computes
IS_TRAININGfromwindow.location(training.eccdps.org, anytraining.*host, or a/trainingpath) and bootsTrainingAppinstead of the normal dashboard when true; - wraps the normal routes in
AuthProvider→ThemeProvider; ProtectedRoutesredirects unauthenticated users to/loginwhile/auth/meis loading or fails.
- computes
Pages
| Route | Component | Purpose |
|---|---|---|
/login | pages/Login.tsx | Discord sign-in entry and OAuth error display |
/dashboard | pages/Dashboard.tsx | Operational overview and recent dockets |
/dockets | pages/Dockets.tsx | Server-paginated, filterable docket list |
/dockets/:id | pages/DocketDetail.tsx | Case detail: timeline, evidence, notes, actions, contact thread |
/evidence | pages/Evidence.tsx | Global evidence workspace |
/analytics | pages/Analytics.tsx | Aggregate metrics (recharts) |
/agents | pages/Agents.tsx | Agent directory |
/agents/:id | pages/Agentdetail.tsx | Agent record detail |
/queue | pages/Queue.tsx | Operator/supervisor action queues with failure recovery |
/audit | pages/AuditLog.tsx | Global timeline/audit history |
/punishments | pages/Punishments.tsx | Discipline records list |
/punishments/:punishment_id | pages/PunishmentDetail.tsx | Single punishment + delivery state |
/admin | pages/Admin.tsx | Capability-gated System Administration (health, queue, audit, agents, access, reports, schema) |
/settings | pages/Settings.tsx | Preferences, theme, avatar |
Components
components/layout/AppLayout.tsx— sidebar navigation, top bar, user identity, sign-out, local clock, and capability-filtered nav (can(permission)for regular items,canAdmin("admin_panel")for admin).components/DiscordProfileCard.tsx/DiscordProfileModal.tsx— stored Discord profile snapshots for parties on a docket.components/GlobalSearch.tsx— global search across dockets, evidence, appeals, users, and the audit log (backed bylib/search/commands.ts).components/NotificationCenter.tsx— notification feed (rendered from API data; see the component for the current source).components/SensitiveField.tsx— masks sensitive identifiers (agent IDs, clearance IDs, agreement IDs); reveal requires clearance 4+ and a double-click confirmation.components/AdminConfirmDialog.tsx— confirmation dialog for admin/destructive actions with reason input.components/DataState.tsx—LoadingState/ErrorStatepresentational components.components/OverlayPortal.tsx— portal helper for overlays.
Shared state model
There is no global application store (no Redux/Zustand). State is:
AuthProvider(src/auth/AuthProvider.tsx) — live agent, permission flags, admin capabilities, loading, refresh, sign-out.ThemeProvider(src/lib/theme.tsx) — applies saved color theme + dark mode to<html>, re-applies on storage/focus events.ToastProvider(src/lib/toast.tsx) — notification toasts.- Page-local state —
useState/useEffect/useCallbackper page; server state is fetched through the API client and held in page state. localStoragepreferences —src/lib/prefs.ts(ecc-dps:prefs): display density, docket filters, date format, theme.- Avatar state — localStorage in
AppLayout/Settings.
Shared utilities
| Module | Purpose |
|---|---|
lib/api/ | Typed API wrappers (client.ts, auth.ts, reports.ts, agents.ts, admin.ts, punishments.ts, types.ts) |
lib/urlSafety.ts | safeHttpUrl / safeImageUrl — URL sanitization at every media/navigation sink |
lib/format.ts | Status/date formatting helpers |
lib/agents.ts | Agent-related helpers |
lib/search/commands.ts | Global search command resources (docket/evidence/appeal/user/log) |
lib/environment.ts | appEnvironment / isDemoEnvironment from hostname or VITE_APP_ENV |
lib/prefs.ts | Preference store |
lib/theme.tsx, lib/toast.tsx | Theme + toast providers |
lib/usePageTitle.ts | Sets document.title per page |
lib/utils.ts | cn() class merger |
Training Center
src/training/ is a self-contained app inside the same bundle:
TrainingApp.tsx— its ownBrowserRouter, providers, and role-branched route trees (trainer routes underTrainingLayout; trainee routes insideTraineeShellfromTrainingRoleGate.tsx). The role branch renders two separate literal<Routes>trees — React Router v7 rejects non-<Route>children directly under<Routes>.- Pages:
TrainingDashboard,TrainingDockets,TrainingSessions,TrainingSessionPage,TrainingLogin,NoActiveTraining. - Components:
TrainerControlCenter.tsx,TraineeInvestigation.tsx. realtime.ts—useTrainingRealtime()hook (private channels, token refresh, presence, status reporting).api.ts/types.ts— training endpoint wrappers and payload types.
Routing and UI architecture
- React Router v7 (
react-router-dom) with a flat<Routes>table inApp.tsx; the Training Center re-normalizes any non-/trainingpath on the training host to its/trainingequivalent. - The UI is presentational-first: pages fetch via the API client, render
LoadingState/ErrorStatewhile in flight, and perform mutations through typed wrappers, then refresh local state from the response or a re-fetch. - Component styling uses Tailwind utility classes with the project's design tokens (
bg-card,text-muted-foreground,border-border, …); new UI should follow the existing patterns rather than introducing new styling systems.