# Dashboard architecture ## Stack and boundary The operator console is a React 19 + TypeScript + Vite application under `apps/dashboard`. Express serves its production build, private media, API, authentication endpoints, and SSE streams from the same origin. TanStack Query owns server state; React Router owns navigation and route-addressable state. Shared HTTP/SSE schemas live in `packages/api-contracts`. The server and client should not duplicate transport interfaces by hand. ## Routes | Route | Responsibility | | --- | --- | | `/` | Health, per-account delivery/cooldown state, timers, workload, and recent activity. | | `/attention` | Human handoffs, delivery failures, account risks, and other actionable items. | | `/outreach` | Durable candidate funnel and active candidate detail. | | `/conversations` | Searchable account-aware conversation inbox. | | `/conversations/:conversationId` | Conversation history, context, suggestions, and guarded sending. | | `/media` | Authenticated, privacy-conscious media gallery and sync controls. | | `/media-library` | Upload, classify, correct, enable, preview, and delete shared exchange images. | | `/performance` | Mature outcome trends, strategy/subreddit results, and model usage. | | `/operations` | Background jobs, sync state, events, rate limits, and technical logs. | | `/settings` | Validated runtime configuration and account/session management. | Navigation is declared in `app/navigation.ts`; route elements are composed in `app/router.tsx`. ## State model - Remote state: TanStack Query snapshots parsed by shared Zod schemas. - Route/filter/selection state: URL/search params when it should survive navigation or be shareable. - Ephemeral UI state: component/provider state, such as dialogs and notification presentation. - Persistent presentation state: guarded browser storage only when explicitly useful (for example notification history). Do not introduce a general global state store unless a demonstrated cross-feature need cannot fit these boundaries. ## HTTP and contracts `api/request.ts` owns authenticated request mechanics and failure conversion. `api/client.ts`/`api/queries.ts` expose typed calls and query keys. Parse every response at the boundary. Strict schemas are deliberate: when the server adds a field, update the shared contract and its consumers in the same change. Mutations should display the confirmed server result. Start/stop, config, sync, import, and send actions must not use optimistic success for operational state. Account delivery state is reported per runtime (`ready`, stopped/failed/login-required, Matrix cooldown, replies prioritized, or pacing). Preserve account attribution when presenting cooldowns and failures; a blocked account does not imply every account is blocked. ## Realtime model `RealtimeProvider` connects to `/api/events/stream`. Event IDs support bounded replay/resume. Each event family either updates a narrowly safe cache value or invalidates the relevant HTTP snapshot. Reconnect and stale detection live in the app layer, not individual feature pages. Rules: - HTTP remains authoritative. - Duplicate/replayed events must be harmless. - A disconnected stream marks data stale but does not blank useful cached content. - Invalid event payloads are contained and repaired with a snapshot refresh. - User-triggered outcomes may create toasts; durable/history-worthy events also appear in the notification center/activity views. - Raw technical logs belong in Operations, not as general user notifications. ## Authentication and privacy The login form exchanges the administrator token for an HttpOnly, SameSite=Strict cookie. API clients may use Bearer tokens. Express applies auth before static dashboard assets, APIs, SSE, and `/media`; `/healthz` is the intentional public exception. Media may be private conversation content. Preserve authenticated URLs, blur/privacy defaults, explicit reveal behavior, safe new-tab links, and no token leakage into query strings or local storage. Exchange-library uploads require an explicit consent checkbox before the raw image request is enabled. Previews remain blurred by default. The management card exposes AI tags, safety level, confidence/review state, enablement, and a two-step delete action. Its progression rail mirrors the actual nonspecific-request selection tiers rather than implying that explicit anatomy requests follow the same sequence. ## Feature structure Each route is a slice under `features//` with page/route components, local styles, and feature-specific helpers. Cross-feature visual/domain components live under `components/`; application lifecycle belongs under `app/`; transport belongs under `api/`. Keep feature components focused: - pages compose queries and sections; - presentational components render typed props; - mutation hooks own cache invalidation; - parsing/formatting lives in small pure helpers; - destructive or external-message actions require explicit confirmation and accessible focus restoration. ## Accessibility and presentation - All actions must be keyboard accessible and have visible focus. - Dialogs label purpose, trap/restore focus, and distinguish cancel from destructive confirmation. - Status must not rely on color alone. - Relative times include an exact timestamp where operationally useful. - Lists are explicitly sorted after filters rather than relying on backend/incidental order. - Loading, empty, stale, partial, and error states are distinct. - Show sample sizes and low-confidence context for performance metrics. ## Testing Dashboard behavior is tested with Vitest, jsdom, Testing Library, and controlled HTTP boundaries. Focus on observable behavior: - representative payloads parse and malformed payloads fail; - route/search state and navigation; - loading/error/empty/stale views; - mutation confirmation and server read-back; - SSE ordering, replay, reconnect, duplicate handling, and snapshot repair; - authentication/private media boundaries; - keyboard/focus behavior for dialogs and controls. Run focused `npx vitest run tests/dashboard/`, then `npm run dashboard:typecheck`, `npm run dashboard:build`, and the full suite before publishing a frontend change. ## Adding a dashboard capability 1. Define/extend the shared Zod contract. 2. Make the server route return that contract and add a route test. 3. Add a typed client/query key. 4. Build the feature slice and observable UI states. 5. Map relevant realtime events to cache updates/invalidations. 6. Add component/realtime/contract coverage. 7. Update `api.md` when the operation catalog changed and update this or the relevant product/flow document.