# Frontend — Familienarchiv

## Overview

SvelteKit 2 application providing the Familienarchiv web UI. Server-side rendered (SSR) where beneficial, with client-side interactivity for document viewing, transcription, annotation, and admin workflows.

## Tech Stack

- **Framework**: SvelteKit 2 with Svelte 5 (runes mode)
- **Language**: TypeScript 5.9
- **Styling**: Tailwind CSS 4.1 + custom brand utilities
- **Build Tool**: Vite 7
- **Adapter**: `@sveltejs/adapter-node` (Node.js server, not static)
- **i18n**: Paraglide.js 2.5 (`@inlang/paraglide-js`) — German (default), English, Spanish
- **API Client**: `openapi-fetch` + `openapi-typescript` (generated from backend OpenAPI spec)
- **PDF Rendering**: `pdfjs-dist` (PDF.js)
- **Testing**:
  - Unit/Server: Vitest 4 (Node environment)
  - Component: Vitest Browser Mode with Playwright (Chromium)
  - E2E: Playwright (`frontend/e2e/`)

## Project Structure

```
src/
├── routes/              # SvelteKit file-based routing
│   ├── +layout.svelte   # Global layout: header, nav, auth state
│   ├── +layout.server.ts # Loads current user, injects auth cookie
│   ├── +page.svelte     # Home / document search dashboard
│   ├── documents/       # Document CRUD, detail, edit, upload
│   ├── persons/         # Person directory, detail, edit, merge
│   ├── briefwechsel/    # Bilateral conversation timeline
│   ├── chronik/         # Unified activity feed
│   ├── admin/           # User, group, tag, OCR, system management
│   ├── api/             # Internal API proxies (server-side only)
│   ├── login/ logout/   # Auth pages
│   └── ...
├── lib/
│   ├── components/      # Reusable Svelte components
│   │   ├── document/    # Document-specific components
│   │   ├── chronik/     # Activity feed components
│   │   └── user/        # User-related components
│   ├── generated/       # Auto-generated API types (openapi-typescript)
│   ├── server/          # Server-only utilities (db, auth helpers)
│   ├── services/        # Client-side service logic
│   ├── stores/          # Svelte stores (global state)
│   ├── types.ts         # Shared TypeScript types
│   ├── errors.ts        # Error code mapping (mirrors backend ErrorCode)
│   ├── api.server.ts    # Typed API client factory
│   ├── utils.ts         # Shared utilities
│   ├── relativeTime.ts  # Time formatting
│   ├── search.ts        # Search utilities
│   └── paraglide/       # Generated i18n code
├── hooks/               # SvelteKit hooks (handle, handleFetch)
└── actions/             # Custom Svelte actions (click outside, etc.)
```

## API Client Pattern

All server-side API calls use the typed client from `$lib/api.server.ts`:

```typescript
const api = createApiClient(fetch);
const result = await api.GET('/api/persons/{id}', { params: { path: { id } } });

// Always check via response.ok, NOT result.error
if (!result.response.ok) {
	const code = (result.error as unknown as { code?: string })?.code;
	throw error(result.response.status, getErrorMessage(code));
}
return { person: result.data! };
```

Key rules:

- Use `!result.response.ok` for error checking (not `if (result.error)` — breaks when spec has no error responses defined)
- Cast errors as `result.error as unknown as { code?: string }` to extract backend error code
- Use `result.data!` after an ok check

For multipart/form-data (file uploads), bypass the typed client and use raw `fetch`.

## Form Actions Pattern

```typescript
// +page.server.ts
export const actions = {
	default: async ({ request, fetch }) => {
		const formData = await request.formData();
		const name = formData.get('name') as string;
		// ...
		return fail(400, { error: 'message' }); // on error
		throw redirect(303, '/target'); // on success
	}
};
```

## Date Handling

- **Forms**: German format `dd.mm.yyyy` with auto-dot insertion via `handleDateInput()`. A hidden `<input type="hidden" name="documentDate" value={dateIso}>` sends ISO to the backend.
- **Display**: Always use `Intl.DateTimeFormat` with `T12:00:00` suffix to prevent UTC off-by-one:
  ```typescript
  new Intl.DateTimeFormat('de-DE', { day: 'numeric', month: 'long', year: 'numeric' }).format(
  	new Date(doc.documentDate + 'T12:00:00')
  );
  ```

## Styling Conventions (Tailwind CSS 4)

Brand color utilities (defined in `layout.css`):

| Class        | Value     | Usage                            |
| ------------ | --------- | -------------------------------- |
| `brand-navy` | `#002850` | Primary text, buttons, headers   |
| `brand-mint` | `#A6DAD8` | Accents, hover underlines, icons |
| `brand-sand` | `#E4E2D7` | Page background, card borders    |

Typography:

- `font-serif` (Merriweather) — body text, document titles, names
- `font-sans` (Montserrat) — labels, metadata, UI chrome

Card pattern for content sections:

```svelte
<div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6">
    <h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-5">Section</h2>
    <!-- content -->
</div>
```

## Key UI Components

| Component            | Props                                                | Description                           |
| -------------------- | ---------------------------------------------------- | ------------------------------------- |
| `PersonTypeahead`    | `name`, `label`, `value`, `initialName`, `on:change` | Single-person selector with typeahead |
| `PersonMultiSelect`  | `selectedPersons` (bind)                             | Chip-based multi-person selector      |
| `TagInput`           | `tags` (bind), `allowCreation?`, `on:change`         | Tag chip input with typeahead         |
| `PdfViewer`          | `url`, `annotations`, `on:annotation`                | PDF rendering with annotation overlay |
| `TranscriptionBlock` | `block`, `mode`                                      | Read/edit transcription block         |
| `DocumentTopBar`     | `document`                                           | Responsive document metadata header   |

## How to Run

### Development

```bash
cd frontend
npm install
npm run dev         # Dev server on port 5173 (or 3000 if --port 3000)
```

### Build & Preview

```bash
npm run build       # Production build
npm run preview     # Preview production build
```

### Code Quality

```bash
npm run lint        # Prettier + ESLint check
npm run format      # Auto-fix formatting
npm run check       # svelte-check (type checking)
```

### Testing

```bash
npm run test              # Vitest unit + server tests (headless)
npm run test:coverage     # Coverage report (server project only)
npm run test:e2e          # Playwright E2E tests
npm run test:e2e:headed   # Playwright E2E with visible browser
npm run test:e2e:ui       # Playwright UI mode
```

### Regenerate API Types

Requires backend running with `--spring.profiles.active=dev`:

```bash
npm run generate:api
```

## Vite Proxy

During development, `/api` calls are proxied to the Spring Boot backend. The proxy injects the `Authorization` header from the `auth_token` cookie automatically (see `vite.config.ts`).

## i18n (Paraglide)

Translations live in `messages/{de,en,es}.json`. The compiler generates type-safe helpers in `src/lib/paraglide/`. Run compilation manually with:

```bash
npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide
```

Or let the Vite plugin handle it automatically during dev/build.
