# 2.1. Frontend Architecture ## Layout of `dashboard/src` ``` src/ ├── App.vue / main.ts # app shell + boot wiring ├── api/ # typed REST layer (api-url, config, conversations, storage, queries/) ├── assets/ # static assets ├── components/ # feature areas: app, chat, sysctl, dlq, pproc, widgets, shared, debug ├── composables/ # cross-area composable logic ├── stores/ # Pinia stores (+ helpers/create-socket-provider) ├── types/ utils/ # shared types and pure helpers └── version.ts # build version constants ``` ## Architectural rules 1. **Components are thin.** A `.vue` file binds props/state to the template and forwards events. Logic lives in _composables_, side-effect-free logic in _helpers_. 2. **Composables do the work.** Cross-area shared logic in `src/composables/`; feature-scoped logic in `components//composables/` (or per-component `composables/` folders). Examples: `use-submit`, `use-chat-input`, `use-chat-think`, `use-socket-subscription`, `use-read-tracker`, `use-action-bar`, `use-blink`, `use-frozen-read-snapshot`, `use-vertical-carousel`, `use-native-scroll`, `use-carousel-blend` (exchange-list scroll modes). 3. **State is Pinia, server state is Query.** Persistent UI/domain state in stores; remote resource fetching via TanStack Query where applicable. 4. **DOM refs via `useTemplateRef`.** Template refs are declared with Vue 3.5's `useTemplateRef('name')` inside composables — never destructured from a composable merely to satisfy a template string ref (that pattern is invisible to `vue-tsc`'s `noUnusedLocals`). ## Stores (`src/stores/`) | Store | Owns | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `app` | Shell state: active area/tab, UI toggles, scroll mode (default + per-conversation), media priority, temporary-conversation retention | | `conversation` | Conversation list, active conversation, exchange models | | `messages` | Incoming API/socket message stream (`addMessage`) | | `session` | Session identity (`sessionId` lifecycle) | | `socket` | Connection lifecycle + callback plumbing to `messages`/`debug` | | `debug` | Socket/API debug journal | | `dlq` | DLQ counter poll, selected envelopes | | `models` | Model catalogue fetched from the harness API | | `preprocessing` | Preprocessing settings, push-to-server sync | | `theme` | Theme selection + persistence | The socket provider (`stores/helpers/create-socket-provider.helper.ts`) is a factory that wires the socket store to message/debug sinks with explicit subscriptions instead of ad-hoc `on()` calls scattered through components. ## API layer (`src/api/`) - `api-url.ts` — base URL composition from `VITE_API_URL`; - `config.api.ts` — SysCtl/config endpoints (HarnessConfig, provider overrides, preprocessing); - `conversations.api.ts` — conversation persistence endpoints; - `playlists.api.ts` — server-side playlist persistence endpoints; - `stock-data.api.ts` — cached end-of-day market history + coverage endpoints (feeds the D3 stock charts); - `warm-model.api.ts` — opt-in model warm-up trigger; - `storage.api.ts` — image payload probes/uploads; - `queries/` — TanStack Query definitions for remote resources. Components never call `fetch` directly — they go through stores/composables which use the API layer. ## Naming & file conventions - Components: `PascalCase.vue`; sibling files colocated per component folder (`Component.vue`, `Component.spec.ts`, `Component.stories.ts`, `composables/`, `helpers/`, `types/`, `styles/`). - Composables: `kebab-case`, `use-` prefix. - Pure functions live in `helpers/` and are unit-testable without mounting anything. - Tests: Vitest + Testing Library (`*.spec.ts`), colocated. Stories: Storybook (`*.stories.ts`) for visual states incl. smoke specs. ## Real-time data flow ``` socket.io-client ─▶ socket store ─(subscriptions)─▶ messages store ─▶ conversation store │ │ └▶ debug store (journal) exchange components render ``` `use-socket-subscription` gives components declarative, auto-cleaned subscriptions; `use-frozen-read-snapshot` + `use-read-tracker` handle scroll/read-state during token streaming without layout jank. ## Quality gates `pnpm lint` (ESLint flat config incl. Vue rules), `vue-tsc -b` as part of `build`, `depcheck`, `dependency-cruiser` (`depcruise`), `ts-unused-exports`. CI enforces all of them (see `non-release.ci.yml`).