Files
RedditBot/docs/frontend.md
T

123 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 browsing sessions: switches, communities, pacing/allowance, next session, optional automatic-upvote control, 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 separates session scheduling from the default-off automatic-upvote switch, lets the operator set the daily target range, minimum gap, and selections per session, and shows whether each selection was submitted or simulated, today's deterministic target, post/comment split, exclusion counts, durable next session, and recent selections with Reddit permalinks. Daily and per-session counts have no product-imposed upper ceiling. The daily range is saved atomically; changing it recalculates today's target, which then remains stable for that range and local day. The displayed minimum gap is followed by bounded random jitter. Scan mode and browser dry-run remain simulation-only. An enabled, running account can also run one immediate session at the configured size; it bypasses the schedule and follows the same automatic-upvote setting. Enabling browsing 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.