123 lines
9.4 KiB
Markdown
123 lines
9.4 KiB
Markdown
# 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. |
|
||
| `/growth` | Per-account comment growth plus simulation-only browsing: switches, communities, pacing/allowance, next browsing session, and both audit trails. |
|
||
| `/settings` | Validated runtime configuration and account/session management. |
|
||
|
||
Navigation is declared in `app/navigation.ts`; route elements are composed in `app/router.tsx`.
|
||
|
||
The account-growth page states plainly that comments are AI-written and posted without review, shows the adaptive allowance and trust level rather than a settable rate, and surfaces every posted comment's exact text alongside its score and removal state. A paused account replaces the pause control with a resume control and explains why it stopped, because resuming is the one action that can compound the damage.
|
||
|
||
Its browsing section separately states that no Reddit votes were submitted, shows today's deterministic target, post/comment split, exclusion counts, durable next session, and recent simulated selections with Reddit permalinks. Enabling browsing changes only the simulation policy; it does not enable comment growth or bot processing.
|
||
|
||
The Monitoring settings section owns the optional physique-targeting toggle and inclusive 1–10 range. It explains that the signal is subjective, applies only to image posts, and screens out unscorable images while enabled.
|
||
|
||
Prepared cards in the authenticated outreach funnel expose the exact opener, grounded image analysis, attractiveness and physique scores, current target range, detected gender, strategy/tone/model metadata, and original source image. The live range is returned through the existing preparation field so cached dashboard bundles remain compatible during a rolling deployment. The durable queue still owns the historical preparation snapshot used for reconciliation. The source image uses the shared `OriginPostCard` privacy behavior: blurred initially, first click reveals it, and a later click opens the full-screen preview. Candidates from older persisted snapshots degrade cleanly when newer optional metadata is absent.
|
||
|
||
## 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. A conversation's source-post thumbnail becomes a keyboard-accessible full-screen preview only after the operator reveals it; the preview closes by button, backdrop, or Escape and restores focus to its trigger.
|
||
|
||
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.
|
||
|
||
Conversation reply composers include a collapsed saved-image picker. It lists only server-approved assets for the latest inbound request, keeps thumbnails blurred until revealed, and requires selection plus a dedicated confirmation dialog before sending. The server remains authoritative and may reject a choice if the conversation changes before the rate-limited send executes.
|
||
|
||
The conversation inbox has a system-derived **Account deleted** bucket alongside the operator-maintained open, snoozed, and done queues. A deleted recipient is excluded from the default, unread, needs-reply, waiting, manual, and image buckets and is labelled in both the list and detail views. Suspended profiles are not treated as deleted.
|
||
|
||
## Feature structure
|
||
|
||
Each route is a slice under `features/<name>/` 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.
|
||
- The fixed primary sidebar is its own vertical scroll region so every route and the platform controls remain reachable on short or low-resolution screens.
|
||
- 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/<file>`, 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.
|