diff --git a/docs/architecture/frontend-build.md b/docs/architecture/frontend-build.md
index 8322a440..7372a884 100644
--- a/docs/architecture/frontend-build.md
+++ b/docs/architecture/frontend-build.md
@@ -1,12 +1,6 @@
# Frontend build system
-This document explains how the frontend codebase is built, what libraries are involved, and how different parts connect at compile time and runtime. It's written for developers who need to modify the build pipeline or understand how the frontend works.
-
-## Overview
-
-The frontend is a Svelte 5 single-page application bundled with Rollup. It uses TypeScript for type safety, Tailwind CSS v4 for styling, and a generated SDK for type-safe API calls. The build outputs static files to `public/build/` which are served by nginx in production or by a custom HTTPS dev server during development.
-
-For details on the Svelte 5 runes API and migration patterns, see [Svelte 5 Migration](svelte5-migration.md).
+The frontend is a Svelte 5 single-page application bundled with Rollup. It uses TypeScript for type safety, Tailwind CSS v4 for styling, and a generated SDK for type-safe API calls. The build outputs static files to `public/build/` which are served by nginx in production or by a custom HTTPS dev server during development. For details on the Svelte 5 runes API and migration patterns, see [Svelte 5 Migration](svelte5-migration.md).
```mermaid
graph LR
@@ -44,11 +38,7 @@ graph LR
## Rollup configuration
-The `rollup.config.js` file configures the entire build pipeline. It produces ES modules with code splitting, enabling parallel loading of vendor code and application code.
-
-### Entry point
-
-The build starts from `src/main.ts`, which imports the API client setup, mounts the Svelte `App` component using Svelte 5's `mount()` function, and imports global CSS:
+The `rollup.config.js` file configures the entire build pipeline, producing ES modules with code splitting for parallel loading of vendor and application code. The build starts from `src/main.ts`, which imports the API client setup, mounts the Svelte `App` component using Svelte 5's `mount()` function, and imports global CSS:
```typescript
import { mount } from 'svelte';
@@ -61,57 +51,20 @@ const app = mount(App, {
});
```
-### Code splitting
-
-Rollup splits the bundle into chunks to improve load performance. The `manualChunks` configuration separates large dependencies:
-
-| Chunk | Contents |
-|-------|----------|
-| `vendor` | Svelte, @mateothegreat/svelte5-router |
-| `codemirror` | All CodeMirror packages for the editor |
-| Application chunks | Route components and shared code |
-
-This means users don't re-download vendor code when application code changes, and the editor chunk only loads when needed.
-
-### Plugins
-
-The plugin pipeline processes files in order:
+Rollup splits the bundle into chunks to improve load performance. The `manualChunks` configuration separates large dependencies into a `vendor` chunk (Svelte, svelte5-router), a `codemirror` chunk (all CodeMirror packages), and application chunks for routes and shared code. This means users don't re-download vendor code when application code changes, and the editor chunk only loads when needed.
-1. **replace** — Substitutes `process.env.VITE_BACKEND_URL` with an empty string, allowing relative API paths
-2. **svelte** — Compiles `.svelte` files with TypeScript preprocessing via `svelte-preprocess`, with `runes: true` enabled for Svelte 5
-3. **postcss** — Processes CSS through PostCSS, extracting styles to `bundle.css`
-4. **typescript** — Compiles TypeScript files with source maps
-5. **json** — Allows importing JSON files
-6. **resolve** — Resolves `node_modules` imports for browser usage, preferring ES modules
-7. **commonjs** — Converts CommonJS modules to ES modules
-8. **terser** (production only) — Minifies JavaScript, removes console logs, runs two compression passes
+The plugin pipeline processes files in order: **replace** substitutes `process.env.VITE_BACKEND_URL` with an empty string for relative API paths; **svelte** compiles `.svelte` files with TypeScript preprocessing and `runes: true` for Svelte 5; **postcss** processes CSS and extracts styles to `bundle.css`; **typescript** compiles TypeScript with source maps; **json** allows importing JSON files; **resolve** handles `node_modules` imports preferring ES modules; **commonjs** converts CommonJS to ES modules; and **terser** (production only) minifies JavaScript, removes console logs, and runs two compression passes.
-### Development server
-
-In development mode (`npm run dev`), Rollup watches for changes and a custom HTTPS server starts automatically. The server handles two responsibilities:
-
-1. **Static file serving** — Serves files from `public/`, falling back to `index.html` for SPA routing
-2. **API proxying** — Forwards `/api/*` requests to the backend container over HTTPS
-
-The proxy uses a custom `https.Agent` that trusts the local CA certificate at `/shared_ca/mkcert-ca.pem`, allowing secure communication with the backend during development. The server listens on port 5001.
+In development mode (`npm run dev`), Rollup watches for changes and a custom HTTPS server starts automatically. The server serves files from `public/` with SPA fallback to `index.html`, and proxies `/api/*` requests to the backend container over HTTPS. The proxy uses a custom `https.Agent` that trusts the local CA certificate at `/shared_ca/mkcert-ca.pem`, allowing secure communication with the backend during development on port 5001.
## TypeScript configuration
-The `tsconfig.json` configures TypeScript compilation:
-
-- Target: ES2020 with ESNext modules
-- Strict mode enabled
-- Module resolution set to bundler mode for Rollup compatibility
-- Svelte component types enabled via `svelte-preprocess`
-
-TypeScript catches type errors during development and the build fails if any exist, preventing broken code from reaching production.
+The `tsconfig.json` targets ES2020 with ESNext modules, enables strict mode, sets module resolution to bundler mode for Rollup compatibility, and enables Svelte component types via `svelte-preprocess`. TypeScript catches type errors during development and the build fails if any exist, preventing broken code from reaching production.
## API SDK generation
The frontend uses a generated SDK for type-safe API calls instead of manual fetch requests. This SDK is created from the backend's OpenAPI specification using `@hey-api/openapi-ts`.
-### Generation pipeline
-
```mermaid
graph LR
OpenAPI["docs/reference/openapi.json"] --> Generator["@hey-api/openapi-ts"]
@@ -120,30 +73,12 @@ graph LR
Generator --> Client["client.gen.ts"]
```
-Run `npm run generate:api` to regenerate the SDK. The configuration in `openapi-ts.config.ts` specifies:
-
-- Input: `../docs/reference/openapi.json` (the backend's OpenAPI spec)
-- Output: `src/lib/api/` with Prettier formatting
-- Plugins: TypeScript types, SDK functions, and fetch client
-
-### Generated files
-
-| File | Purpose |
-|------|---------|
-| `types.gen.ts` | TypeScript interfaces for all request/response models |
-| `sdk.gen.ts` | Function for each API endpoint, fully typed |
-| `client.gen.ts` | HTTP client with interceptor support |
-| `index.ts` | Re-exports types and SDK functions |
-| `setup.ts` | Manual file that configures the client (not generated) |
-
-### Client configuration
-
-The `setup.ts` file configures the generated client:
+Run `npm run generate:api` to regenerate the SDK. The configuration in `openapi-ts.config.ts` specifies the input as `../docs/reference/openapi.json`, outputs to `src/lib/api/` with Prettier formatting, and generates TypeScript types, SDK functions, and a fetch client. The generated files include `types.gen.ts` (TypeScript interfaces for all request/response models), `sdk.gen.ts` (functions for each API endpoint, fully typed), `client.gen.ts` (HTTP client with interceptor support), and `index.ts` (re-exports for convenient imports). There's also a manual `setup.ts` file that configures the client with relative URLs and cookie credentials, and adds an interceptor that automatically attaches CSRF tokens to mutating requests:
```typescript
client.setConfig({
- baseUrl: '', // Relative URLs, proxied in dev
- credentials: 'include', // Send cookies for auth
+ baseUrl: '',
+ credentials: 'include',
});
client.interceptors.request.use((request) => {
@@ -155,121 +90,50 @@ client.interceptors.request.use((request) => {
});
```
-The interceptor automatically adds CSRF tokens to mutating requests, pulling the token from the auth store. This happens transparently for all SDK calls.
-
-### Usage pattern
-
-Components import SDK functions and types directly:
+Components import SDK functions and types directly. The SDK returns `{ data, error }` tuples, making error handling explicit without try/catch boilerplate:
```typescript
-import {
- getNotificationsApiV1NotificationsGet,
- type NotificationResponse,
-} from '../lib/api';
+import { getNotificationsApiV1NotificationsGet, type NotificationResponse } from '../lib/api';
-const { data, error } = await getNotificationsApiV1NotificationsGet({
- query: { limit: 20 }
-});
+const { data, error } = await getNotificationsApiV1NotificationsGet({ query: { limit: 20 } });
```
-The SDK returns `{ data, error }` tuples, making error handling explicit without try/catch boilerplate.
-
## Tailwind CSS v4
-The frontend uses Tailwind CSS v4 with the new CSS-first configuration. Unlike v3, there's no `tailwind.config.js` — all configuration lives in CSS.
-
-### PostCSS integration
-
-PostCSS processes CSS through `@tailwindcss/postcss`:
-
-```javascript
-// postcss.config.cjs
-module.exports = {
- plugins: {
- "@tailwindcss/postcss": {},
- },
-}
-```
-
-### CSS configuration
-
-The `src/app.css` file contains all Tailwind configuration using v4's new at-rules:
+The frontend uses Tailwind CSS v4 with the new CSS-first configuration. Unlike v3, there's no `tailwind.config.js` — all configuration lives in CSS. PostCSS processes CSS through `@tailwindcss/postcss`, and the `src/app.css` file contains all Tailwind configuration using v4's new at-rules:
```css
-/* Import Tailwind */
@import "tailwindcss";
-/* Forms plugin */
-@plugin "@tailwindcss/forms" {
- strategy: class;
-}
-
-/* Class-based dark mode */
+@plugin "@tailwindcss/forms" { strategy: class; }
@variant dark (&:where(.dark, .dark *));
-/* Custom theme tokens */
@theme {
--color-primary: #3b82f6;
--color-bg-default: #f8fafc;
--font-sans: 'Inter', ui-sans-serif, system-ui;
- /* ... */
}
-/* Custom utilities */
@utility animate-fadeIn {
- animation: fadeIn 0.3s ease-in-out;
+ animation: var(--animate-fadeIn);
}
```
-### Theme structure
-
-The theme defines semantic color tokens for both light and dark modes:
-
-| Token | Light | Dark |
-|-------|-------|------|
-| `bg-default` | `#f8fafc` | `#0f172a` |
-| `fg-default` | `#1e293b` | `#e2e8f0` |
-| `border-default` | `#e2e8f0` | `#334155` |
-
-Components use these tokens (e.g., `bg-bg-default dark:bg-dark-bg-default`) for consistent theming. The `@variant dark` rule enables the `.dark` class on `` to trigger dark mode.
+The theme defines semantic color tokens for both light and dark modes (`bg-default`, `fg-default`, `border-default`, etc.) which components use for consistent theming. The `@variant dark` rule enables the `.dark` class on `` to trigger dark mode. Styles are organized into Tailwind layers: **base** for element defaults, form styles, scrollbars, and CodeMirror overrides; **components** for reusable patterns like `.btn`, `.card`, and `.form-input-standard`.
-### Layer organization
+## Svelte stores and state
-Styles are organized into Tailwind layers:
+The frontend uses a hybrid approach to state management. Svelte stores in `src/stores/` handle global, shared state: `auth.ts` manages authentication state, login/logout, and CSRF tokens; `theme.ts` handles theme preference with localStorage persistence; `toastStore.ts` manages the toast notification queue; and `notificationStore.ts` handles server notifications with pagination. Stores use the generated SDK for API calls and persist state to localStorage where appropriate. The auth store exposes a `csrfToken` store that the API client interceptor reads for request signing.
-- **base** — Element defaults, form styles, scrollbars, CodeMirror overrides
-- **components** — Reusable patterns like `.btn`, `.card`, `.form-input-standard`
-
-## Svelte stores and runes
-
-The frontend uses a hybrid approach to state management:
-
-**Svelte stores** (`src/stores/`) handle global, shared state:
-
-| Store | Purpose |
-|-------|---------|
-| `auth.ts` | Authentication state, login/logout, CSRF token |
-| `theme.ts` | Theme preference (light/dark/auto) with localStorage persistence |
-| `toastStore.ts` | Toast notifications queue |
-| `notificationStore.ts` | Server notifications with pagination |
-
-Stores use the generated SDK for API calls and persist state to localStorage where appropriate. The auth store exposes a `csrfToken` store that the API client interceptor reads for request signing.
-
-**Svelte 5 runes** (`$state`, `$derived`, `$effect`) handle component-local state:
+Svelte 5 runes (`$state`, `$derived`, `$effect`) handle component-local state. Store subscriptions remain unchanged from Svelte 4 — the `$storeName` syntax auto-subscribes to any store:
```svelte
{#if $isAuthenticated}
@@ -279,14 +143,6 @@ Stores use the generated SDK for API calls and persist state to localStorage whe
For detailed patterns and migration guidance, see [Svelte 5 Migration](svelte5-migration.md).
-## Build commands
-
-| Command | Purpose |
-|---------|---------|
-| `npm run dev` | Start Rollup in watch mode with HTTPS dev server |
-| `npm run build` | Production build with minification |
-| `npm run generate:api` | Regenerate SDK from OpenAPI spec |
-
## File structure
```
@@ -313,64 +169,30 @@ frontend/
└── openapi-ts.config.ts # SDK generator config
```
-## Local development
-
-Start the development stack:
-
-```bash
-# From project root
-docker compose up -d
-
-# In frontend directory
-npm install
-npm run dev
-```
-
-The dev server runs at `https://localhost:5001`. API requests proxy to the backend container. Changes to `.svelte`, `.ts`, and `.css` files trigger automatic rebuilds.
-
-### Regenerating the API client
+## Build commands
-When backend endpoints change:
+| Command | Purpose |
+|---------|---------|
+| `npm run dev` | Start Rollup in watch mode with HTTPS dev server |
+| `npm run build` | Production build with minification |
+| `npm run generate:api` | Regenerate SDK from OpenAPI spec |
-1. Update the backend and restart it
-2. Fetch the new OpenAPI spec (the docs workflow does this automatically)
-3. Run `npm run generate:api`
-4. Fix any TypeScript errors from changed types
+## Local development
-### Adding new routes
+Start the development stack from the project root with `docker compose up -d`, then in the frontend directory run `npm install && npm run dev`. The dev server runs at `https://localhost:5001` with API requests proxying to the backend container. Changes to `.svelte`, `.ts`, and `.css` files trigger automatic rebuilds.
-1. Create a component in `src/routes/`
-2. Add a `` entry in `App.svelte`
-3. Use SDK functions for API calls
-4. Use semantic color tokens for styling
+When backend endpoints change, update the backend and restart it, fetch the new OpenAPI spec (the docs workflow does this automatically), run `npm run generate:api`, and fix any TypeScript errors from changed types. To add new routes, create a component in `src/routes/`, add a `` entry in `App.svelte`, use SDK functions for API calls, and use semantic color tokens for styling.
## Production build
-The production build runs `npm run build`, which:
-
-1. Compiles TypeScript with source maps
-2. Processes Svelte components in production mode (no dev warnings)
-3. Extracts and minifies CSS
-4. Splits code into chunks
-5. Minifies JavaScript with Terser (removes console.log)
-6. Outputs to `public/build/`
-
-The Docker build copies `public/` to nginx, which serves static files and proxies `/api/` to the backend.
+The production build runs `npm run build`, which compiles TypeScript with source maps, processes Svelte components in production mode without dev warnings, extracts and minifies CSS, splits code into chunks, minifies JavaScript with Terser removing console.log calls, and outputs everything to `public/build/`. The Docker build copies `public/` to nginx, which serves static files and proxies `/api/` to the backend.
## Troubleshooting
-### TypeScript errors after SDK regeneration
-
-If the backend changed response types, update components to match. The SDK provides exact types — check `types.gen.ts` for the new structure.
-
-### Styles not applying
-
-Ensure the class exists in Tailwind's default utilities or is defined in `app.css`. Check for typos in semantic token names (e.g., `bg-default` vs `bg-bg-default`).
-
-### Dev server certificate errors
+If you see TypeScript errors after SDK regeneration, check `types.gen.ts` for the new structure and update components to match the changed response types.
-The dev server requires certificates at `./certs/server.key` and `./certs/server.crt`, and the CA at `/shared_ca/mkcert-ca.pem`. Run the cert-generator container first via Docker Compose.
+For styles not applying, ensure the class exists in Tailwind's default utilities or is defined in `app.css`, and check for typos in semantic token names (e.g., `bg-default` vs `bg-bg-default`).
-### API calls failing in development
+Dev server certificate errors mean the certificates at `./certs/server.key` and `./certs/server.crt` are missing, or the CA at `/shared_ca/mkcert-ca.pem` isn't available — run the cert-generator container first via Docker Compose.
-Verify the backend is running and healthy. The dev server proxies to `https://backend:443` — check Docker networking if the container can't resolve the hostname.
+If API calls fail in development, verify the backend is running and healthy; the dev server proxies to `https://backend:443`, so check Docker networking if the container can't resolve the hostname.
diff --git a/docs/architecture/svelte5-migration.md b/docs/architecture/svelte5-migration.md
index 684821de..3902b245 100644
--- a/docs/architecture/svelte5-migration.md
+++ b/docs/architecture/svelte5-migration.md
@@ -1,13 +1,8 @@
# Svelte 5 migration
-This document explains the migration from Svelte 4 to Svelte 5, covering the new runes API, reactivity model, and
-practical patterns used throughout the frontend. It serves as both a reference for understanding the current codebase
-and a guide for future development.
-
## Why Svelte 5
-Svelte 5 introduces **runes** — a new reactivity system that replaces Svelte 4's implicit reactivity with explicit
-declarations. The key benefits:
+Svelte 5 introduces runes — a new reactivity system that replaces Svelte 4's implicit reactivity with explicit declarations.
| Svelte 4 | Svelte 5 |
|---------------------------------------------------------------------|------------------------------------------------------------------|
@@ -16,13 +11,11 @@ declarations. The key benefits:
| `$:` reactive statements mixed derivations and side effects | `$derived` and `$effect` separate concerns |
| Slots for component composition | Snippets provide more flexible composition |
-The migration also updated the router from `svelte-routing` to `@mateothegreat/svelte5-router`, which is designed for
-Svelte 5's component model.
+The migration also updated the router from `svelte-routing` to `@mateothegreat/svelte5-router`, which is designed for Svelte 5's component model.
## Runes overview
-Runes are compiler directives that look like function calls but are processed at compile time. They're available in
-`.svelte` files and `.svelte.ts` files without imports.
+Runes are compiler directives that look like function calls but are processed at compile time. They're available in `.svelte` files and `.svelte.ts` files without imports.
### $state
@@ -30,14 +23,9 @@ Creates reactive state that triggers UI updates when modified:
```svelte
```
@@ -87,8 +57,7 @@ Creates computed values that automatically update when dependencies change:
let items = $state([]);
let filter = $state('');
- // Simple derivation
- let count = $derived(items.length);
+ let count = $derived(items.length); // Simple derivation
// Complex derivation with $derived.by
let filteredItems = $derived.by(() => {
@@ -100,8 +69,7 @@ Creates computed values that automatically update when dependencies change:
```
-Use `$derived` for any value computed from reactive state. Use `$derived.by` when the computation needs multiple
-statements.
+Use `$derived` for any value computed from reactive state. Use `$derived.by` when the computation needs multiple statements.
### $effect
@@ -112,24 +80,13 @@ Runs side effects when dependencies change:
let theme = $state('dark');
$effect(() => {
- // Runs when theme changes
document.documentElement.classList.toggle('dark', theme === 'dark');
-
- // Optional cleanup function
- return () => {
- console.log('Cleaning up previous effect');
- };
+ return () => console.log('Cleaning up previous effect'); // Optional cleanup
});
```
-!!! warning "Use $effect sparingly"
-Most reactive needs are better served by `$derived`. Use `$effect` only for:
-
- - DOM manipulation outside Svelte's control
- - External library integration
- - Subscriptions and event listeners
- - Logging and debugging
+Use `$effect` sparingly — most reactive needs are better served by `$derived`. Reserve effects for DOM manipulation outside Svelte's control, external library integration, subscriptions and event listeners, and logging/debugging.
### $props
@@ -137,11 +94,8 @@ Declares component props with destructuring:
```svelte