Files
ss-tools/docs/adr/ADR-0006-frontend-architecture.md
2026-05-08 18:01:49 +03:00

80 lines
5.0 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.

# [DEF:ADR-0006:ADR]
# @STATUS ACTIVE
# @PURPOSE Define the frontend architecture for ss-tools: Svelte 5 runes reactivity model, SvelteKit routing, component composition rules, state management patterns, API client conventions, and UX contract expectations for C4/C5 components.
# @RELATION DEPENDS_ON -> [ADR-0001:ADR]
# @RELATION DEPENDS_ON -> [ADR-0002: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) ss-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.
# @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.
## 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 |
| 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 ... ❌ stores are .svelte.js modules with $state
```
### State Management Pattern
Three tiers of state, strictly separated:
1. **Componentlocal state**: `$state()` inside `.svelte` files. Never exported.
2. **Shared reactive state**: `.svelte.js` modules exporting `$state` objects. Imported by components.
```js
// frontend/src/lib/stores/dashboard.svelte.js
export const dashboardState = $state({
dashboards: [],
selectedId: null,
isLoading: false
});
```
3. **Server state**: Fetched via API client, held in `$state` variables locally. No global cache — each route fetches what it needs.
### 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 `semantics-frontend` skill (`.opencode/skills/semantics-frontend/SKILL.md`). The skill is the canonical source for tag inventory, syntax, and percomplexity requirements.
### Component File Structure
```
frontend/src/lib/components/
├── DashboardGrid.svelte # C4: complex data grid with WebSocket updates
├── MappingTable.svelte # C3: migration mapping table
├── PluginExecutionCard.svelte # C4: plugin runner with progress feedback
├── RoleBadge.svelte # C1: simple role display
└── ...
```
### Routing
- SvelteKit filebased routing under `frontend/src/routes/`
- Protected routes check auth in `+layout.svelte` (redirect to `/login` if no token)
- Route structure: `/` (dashboard list), `/envs/[id]` (environment detail), `/migration` (migration wizard), `/plugins` (plugin management), `/admin` (user/role management)
# [/DEF:ADR-0006:ADR]