Files
ss-tools/docs/adr/ADR-0006-frontend-architecture.md
busya ec6421de35 rename ss-tools to superset-tools across the entire project
- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files
- Rename git bundle file ss-tools.bundle → superset-tools.bundle
- Update .gitignore pattern accordingly
- Preserve variable names (hasSsTools etc.) and code identifiers
2026-06-16 11:15:19 +03:00

17 KiB
Raw Blame History

[DEF:ADR-0006:ADR]

@STATUS ACTIVE

@PURPOSE Define the frontend architecture for superset-tools: Svelte 5 runes reactivity model, SvelteKit routing, component composition rules, state management patterns (incl. Screen Models), design token system, UI atom reuse, API client conventions, and UX contract expectations for C4/C5 components.

@RELATION DEPENDS_ON -> [ADR-0001:ADR]

@RELATION DEPENDS_ON -> [ADR-0002:ADR]

@RELATION DEPENDS_ON -> [ADR-0010:ADR]

@RATIONALE Svelte 5 introduces a fundamentally different reactivity model (runes: $state, $derived, $effect, $props) compared to Svelte 4's $: reactive statements. This ADR locks in the Svelte 5 runes approach and prevents backsliding into legacy patterns when new developers or AI agents contribute code.

@RATIONALE SvelteKit with static adapter (@sveltejs/adapter-static) was chosen over SvelteKit SSR because: (a) superset-tools is a Dockerdeployed SPA behind nginx — serverside rendering provides no latency benefit, (b) static SPA simplifies Docker deployment (no Node.js server needed, just nginx serving static files), (c) all data is fetched via REST API, not serverside load functions.

@RATIONALE Svelte 5 runes ($state, $derived, $effect) are mandated over Svelte 4 $: reactivity and writable stores because: (a) runes are the forward path — Svelte 4 patterns are deprecated, (b) runes provide finegrained reactivity without subscription boilerplate, (c) $state can be used outside .svelte files in .svelte.js modules, enabling reusable reactive logic.

@RATIONALE Design tokens (semantic colors in tailwind.config.js) were introduced in 20260602 (PR 01) to eliminate raw Tailwind classes (bgblue600, textgray900) from pagelevel code. This prevents visual drift and gives AI agents a single source of truth for color choices. See §"Design Token System" below.

@RATIONALE Screen Model pattern ([TYPE Model] in .svelte.ts files) was formalised in 20260602 (PR 34) to extract crosswidget state from page files. Models reduce page files from 2700+ lines to <1400 lines and enable unittesting of invariants without DOM render (~10ms). See §"State Management Pattern" and ADR0010.

@REJECTED React/Next.js — rejected by ADR-0003 (technology stack independence from Superset). Svelte 5 was chosen for its compiletime approach, smaller bundle size, and superior DX for this project's scale.

@REJECTED Svelte 4 legacy patterns ($:, writable/readable stores) — rejected because Svelte 5 runes are the actively maintained reactivity model. Mixing two models creates confusion and potential reactivity bugs.

@REJECTED ServerSide Rendering (SSR) with SvelteKit — rejected because the data is entirely APIdriven (no serverside page data loading), and SSR adds deployment complexity (Node.js process) without latency benefit for authenticated SPA users.

@REJECTED Raw Tailwind color classes (bgblue600, textgray900, borderindigo300, etc.) in page and component code — rejected because they create visual drift across 70+ files (3892 violations catalogued 20260602). Replaced by semantic token system (see §"Design Token System").

@REJECTED Inline $state + handler functions in complex pages (>5 $state atoms) — rejected because they produce monolithic, untestable page files (DashboardHub was 2765 lines before extraction). Replaced by Screen Model pattern (see §"State Management Pattern").

Decision

Technology Stack

Layer Choice Version
Framework SvelteKit 2.x
UI Library Svelte 5.x (runes mode)
Build Vite 7.x
Styling Tailwind CSS 3.x + semantic design tokens 3.x
Adapter @sveltejs/adapter-static 3.x (SPA mode)
Testing vitest + @testing-library/svelte 4.x / 5.x

Reactivity Model (Svelte 5 Runes)

Legacy (FORBIDDEN)              Svelte 5 Runes (REQUIRED)
────────────────────────        ──────────────────────────
let count = 0;                  let count = $state(0);
$: doubled = count * 2;         let doubled = $derived(count * 2);
$: { /* side effect */ }        $effect(() => { /* side effect */ });
export let name;                let { name } = $props();
import { writable } from ...    ❌ new stores MUST use .svelte.ts with $state
                                ⚠️  8 existing writable stores are legacy — migrate, don't extend

Note on legacy stores: 8 stores (sidebar.ts, taskDrawer.ts, environmentContext.ts, datasetReviewSession.ts, assistantChat.ts, health.ts, activity.ts, translationRun.ts) still use writable() from svelte/store. These predate Svelte 5 adoption. New reactive state MUST use .svelte.ts files with $state. Existing stores are read via $store syntax in .svelte files or get() in .svelte.ts modules. ADR0007 documents the fromStore + $derived rejection.

State Management Pattern

Four tiers of state, strictly separated:

Tier Pattern File Use case Example
1. Componentlocal $state() in .svelte <Component>.svelte Accordion open, tooltip visible, input focus let isOpen = $state(false)
2. Screen Model class with $state + methods .svelte.ts in lib/models/ Crosswidget state with invariants (filters, pagination, search, multistep) DashboardHubModel, MigrationModel
3. Global store $state in .svelte.ts module .svelte.ts in lib/stores/ Crossroute state (auth, notifications, sidebar) maintenance.svelte.ts
4. Server state Fetched via API, held in Model or component N/A HTTP responses from backend api.getDashboards() → model atom

Screen Model (tier 2) — the modelfirst pattern:

For any page with >5 $state atoms or crosswidget invariants (filter resets pagination, selection survives search), create a [TYPE Model] in lib/models/ BEFORE implementing components. The Model declares @STATE, @ACTION, @INVARIANT tags and is testable without DOM render.

// frontend/src/lib/models/DashboardHubModel.svelte.ts
export class DashboardHubModel {
  // Atoms
  dashboards: DashboardRow[] = $state([]);
  page: number = $state(1);
  searchQuery: string = $state("");
  // ... < 40 atoms

  // Actions
  async loadDashboards(): Promise<void> { ... }
  setPage(n: number): void { ... }
  // ... < 40 methods
}

Page file becomes a thin render layer:

<script lang="ts">
  import { DashboardHubModel } from "$lib/models/DashboardHubModel.svelte.ts";
  const m = new DashboardHubModel();
</script>
<!-- template uses m.dashboards, m.page, onClick={() => m.setPage(2)} -->

Decomposition gate (ADR0010): If a Model exceeds 400 lines or 40 public methods, split into submodels by responsibility domain (e.g. DashboardFiltersModel, DashboardSelectionModel, DashboardGitModel).

Design Token System

Raw Tailwind color classes are DEPRECATED in page and component code. All colors MUST use semantic tokens defined in frontend/tailwind.config.js. This is the single source of truth for the visual system.

Token families (from tailwind.config.js):

Purpose Token Maps to
Primary action bg-primary text-white blue600
Page background bg-surface-page slate50
Card surface bg-surface-card white
Muted surface bg-surface-muted slate100
Default border border-border slate200
Strong border (inputs) border-border-strong slate300
Primary text text-text slate900
Muted text text-text-muted slate500
Subtle text (placeholders) text-text-subtle slate400
Destructive / error bg-destructive-light text-destructive border-destructive-ring red family
Success bg-success-light text-success green family
Warning bg-warning-light text-warning amber family
Info bg-info-light text-info sky family

Correct vs. wrong:

// ✅ CORRECT — semantic tokens
<div class="bg-surface-card border border-border text-text">
<Button variant="primary">

// ❌ WRONG — raw Tailwind (forbidden in page/component code)
<div class="bg-white border border-gray-200 text-gray-900">
<button class="bg-blue-600 text-white">

Enforcement: scripts/audit-frontend-style.mjs scans src/routes/** and src/lib/components/** for raw color violations. CI rejects PRs with raw colors. Canonical token reference and copypaste examples live in semanticssvelte §VII.

UI Atom Reuse

Pagelevel UI MUST use $lib/ui atoms. Raw <button> elements and manual <div class="bg-white rounded..."> cards in page files are a violation.

Atom Import Variants
Button from "$lib/ui" primary (default), secondary, destructive (канонический), ghost. danger — deprecated alias
Card from "$lib/ui" padding="md" (default), none, sm, lg; optional title
Input from "$lib/ui" label, error, disabled, type; optional id
Select from "$lib/ui" label, options, disabled; optional id
PageHeader from "$lib/ui" title, optional subtitle slot, actions slot
EmptyState from "$lib/ui" title, description, optional actionLabel + onAction
Icon from "$lib/ui" name, size, class, strokeWidth
HelpTooltip from "$lib/ui" text tooltip
LanguageSwitcher from "$lib/ui" lang toggle

Atoms are the ONLY place where raw Tailwind is allowed — they encapsulate design tokens so pages don't need to.

Component File Structure

Three directories, different rules:

frontend/src/lib/ui/                     # UI atoms (9 files) — MANDATORY for pages
├── Button.svelte, Card.svelte, Input.svelte, Select.svelte
├── PageHeader.svelte, EmptyState.svelte, Icon.svelte
├── HelpTooltip.svelte, LanguageSwitcher.svelte
└── index.ts                             # Barrel export

frontend/src/lib/components/             # Domain components (30+ files) — NEW components go HERE
├── layout/    (Sidebar, TopNavbar, TaskDrawer, Breadcrumbs)
├── translate/ (TranslationPreview, BulkReplaceModal, ...)
├── reports/   (ReportsList, ReportCard, ReportDetailPanel)
├── dataset-review/ (ValidationFindingsPanel, SourceIntakePanel, ...)
├── health/    (HealthMatrix, ScheduleAtAGlance, PolicyForm)
├── llm/       (ValidationTaskForm, UrlParser, ValidationTaskReport)
├── ui/        (SearchableMultiSelect, MultiSelect, DatasetSearchCombobox)
├── assistant/ (AssistantChatPanel, AssistantClarificationCard)
├── settings/  (ApiKeysTab)
└── (DashboardMaintenanceBadge, MaintenanceEventsTable, ...)

frontend/src/components/                 # LEGACY FROZEN (40 files) — do NOT add, migrate out
├── auth/ git/ tasks/ tools/ backups/ storage/ llm/
├── EnvSelector, DashboardGrid, MappingTable, TaskRunner, TaskHistory
├── TaskLogViewer, Toast, Navbar, Footer, PasswordPrompt
└── StartupEnvironmentWizard, DynamicForm, MissingMappingModal

Rules:

  • New domain components → lib/components/<domain>/
  • src/components/ — заморожено. Новые файлы запрещены. Существующие — мигрировать в lib/components/ при рефакторинге.
  • svelte.config.js alias $components → src/components помечен @DEPRECATED.

API Client Convention

API client modules live under frontend/src/lib/api/. Each module wraps fetch calls to the FastAPI backend, attaches the JWT access token from the auth store, and normalises errors into a typed ApiError. Contract annotations follow the rules defined in the semantics-core and semantics-contracts skills.

Component Complexity & UX Contracts

Svelte components with side effects (API calls, WebSocket subscriptions, file operations) MUST carry UX contract tags as defined by the semanticssvelte skill (.opencode/skills/semanticssvelte/SKILL.md). The skill is the canonical source for tag inventory (@UX_STATE, @UX_FEEDBACK, @UX_RECOVERY, @UX_REACTIVITY, @UX_TEST), syntax, and percomplexity requirements.

Routing

SvelteKit filebased routing under frontend/src/routes/. Protected routes check auth in +layout.svelte (redirect to /login if no token).

Actual route structure (verified 20260602):

Route Page Complexity
/login Login page C1
/ (redirect → /dashboards) Dashboard hub C4 → C4+Model
/dashboards Dashboard hub with git status, validation badges, bulk actions C4
/dashboards/[id] Dashboard detail: metadata, charts, datasets, git, task history C4
/dashboards/[id]/validation Validation status and run history C3
/dashboards/health Dashboard health matrix C3
/datasets Dataset hub with mapping progress, split view C4
/datasets/[id] Dataset detail C3
/datasets/review Dataset review session list C3
/datasets/review/[id] Review workspace: semantic layer, SQL, execution mapping C5
/migration Migration wizard (env selection → dashboards → review → execute) C4
/migration/mappings Migration database mappings C3
/reports Unified reports with tasktype and status filtering C3
/reports/llm/[taskId] LLM validation report C3
/validationtasks Validation tasks list with status filter, pagination C3
/validationtasks/new Create validation task C3
/validationtasks/[policyId] Task detail + runs C3
/validationtasks/[policyId]/edit Edit validation task C3
/validationtasks/[policyId]/runs/[runId] Run detail with logs C4
/validationtasks/history Task run history C3
/settings Consolidated settings (environments, logging, LLM, migration, storage, features, system) C4
/settings/git Git configuration C3
/settings/notifications Notification preferences C2
/settings/automation Automation rules C3
/profile User profile and preferences C2
/translate Translation hub: dictionaries + configs C4
/translate/[id] Translation config detail C4
/translate/dictionaries Dictionary list C3
/translate/dictionaries/[id] Dictionary detail with entries C4
/translate/history Translation run history C3
/admin/users User management (RBAC) C3
/admin/roles Role management C3
/admin/settings Adminlevel settings C3
/admin/settings/llm LLM provider configuration C3
/git Git operations C3
/storage Storage management C3
/storage/repos Repository list C3
/storage/backups Backup management C3
/tools/mapper Data mapper tool C3
/tools/debug Debug tool C3
/tools/storage Storage tool C3
/tools/backups Backup tool C3
/maintenance Maintenance events and settings C3

Guardrail Enforcement

scripts/audit-frontend-style.mjs — CIready script that checks:

  1. Raw color violationsbgblue*, textgray*, borderindigo*, etc. in page and component files
  2. Oversized pages — pages > 400 lines (INV_7)
  3. Modelfirst violations — pages with >5 $state atoms and no Model import
  4. Manual <button> warnings — pages with <button> but no <Button> import
  5. Legacy directory — files remaining in frozen src/components/

Exit code 1 on violations → CI blocks merge.

Consequences

Positive

  • Design tokens eliminate visual drift — AI agents get a single color palette.
  • Screen Models make complex pages testable without DOM (~10ms unit tests).
  • Guardrail catches violations before merge.
  • UI atoms ensure consistent interaction patterns (loading states, error borders, focus rings).

Negative

  • Legacy writable stores require $store syntax in .svelte files and get() in .svelte.ts — a bridge between Svelte 4 and 5 that must be maintained until full migration.
  • src/components/ still exists as a frozen directory — 40 files to migrate.
  • Screen Model thiscontext requires arrow wrappers (onclick={() => m.method()}) — a pitfall documented in ainativedesignaudit.md §10.

References

  • ADR0001 — Module Layout
  • ADR0002 — Semantic Protocol
  • ADR0007 — fromStore + $derived rejection
  • ADR0010 — Model Decomposition Gate
  • semanticssvelte skill — UI implementation rules, canonical template
  • ainativedesignaudit.md — design audit findings and execution log

[/DEF:ADR-0006:ADR]