Skip to content

2.1 architecture

wiki[bot] edited this page Aug 23, 2026 · 4 revisions

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/<area>/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).

Clone this wiki locally