# CLAUDE.md

> For a human-readable project overview, see [README.md](./README.md).

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

> For a human-readable project overview, see [README.md](./README.md).

## Project Overview

**Familienarchiv** is a family document archival system — a full-stack web app for digitizing, organizing, and searching family documents. Key features: file uploads (stored in MinIO/S3), metadata management, Excel/ODS batch import, full-text search, conversation threads between family members, and role-based access control.

## Collaboration

See [COLLABORATING.md](./COLLABORATING.md) for the full rules: issue tracking workflow, commit message conventions, and the Research → Plan → Implement → Validate cycle.

See [CODESTYLE.md](./CODESTYLE.md) for coding standards: Clean Code, DRY/KISS trade-offs (KISS wins), and SOLID principles applied to this stack.

---

## Stack

→ See [README.md §Tech Stack](./README.md#tech-stack)

- **Backend**: Spring Boot 4.0 (Java 21, Maven, Jetty, JPA/Hibernate, Flyway, Spring Security, Spring Session JDBC)
- **Frontend**: SvelteKit 2 with Svelte 5, TypeScript, Tailwind CSS 4, Paraglide.js (i18n: de/en/es)
- **Database**: PostgreSQL 16
- **Object Storage**: MinIO (S3-compatible)
- **Infrastructure**: Docker Compose

## Common Commands

### Running the Full Stack

```bash
docker-compose up -d
```

### Backend (Spring Boot)

```bash
cd backend

./mvnw spring-boot:run          # Run locally
./mvnw clean package            # Build JAR (with tests)
./mvnw clean package -DskipTests
./mvnw test                     # Run all tests
./mvnw test -Dtest=ClassName    # Run a single test class
```

### Frontend (SvelteKit)

```bash
cd frontend

npm install
npm run dev         # Dev server (port 5173)
npm run build       # Production build
npm run preview     # Preview production build

npm run lint        # Prettier + ESLint check
npm run format      # Auto-fix formatting
npm run check       # svelte-check (type checking)
npm run test        # Vitest unit tests
npm run generate:api  # Regenerate TypeScript API types from OpenAPI spec
                      # (requires backend running with --spring.profiles.active=dev)
```

---

## Backend Architecture

### Package Structure

<!-- TODO: rewrite post-REFACTOR-1 — see Epic 4 -->

```
backend/src/main/java/org/raddatz/familienarchiv/
├── audit/               Audit logging
├── config/              Infrastructure config (Minio, Async, Web)
├── dashboard/           Dashboard analytics + StatsController/StatsService
├── document/            Document domain (entities, controller, service, repository, DTOs)
│   ├── annotation/      DocumentAnnotation, AnnotationService, AnnotationController
│   ├── comment/         DocumentComment, CommentService, CommentController
│   └── transcription/   TranscriptionBlock, TranscriptionService, TranscriptionBlockQueryService
├── exception/           DomainException, ErrorCode, GlobalExceptionHandler
├── filestorage/         FileService (S3/MinIO)
├── geschichte/          Geschichte (story) domain
├── importing/           MassImportService
├── notification/        Notification domain + SseEmitterRegistry
├── ocr/                 OCR domain — OcrService, OcrBatchService, training
├── person/              Person domain
│   └── relationship/    PersonRelationship sub-domain
├── security/            SecurityConfig, Permission, @RequirePermission, PermissionAspect
├── tag/                 Tag domain
└── user/                User domain — AppUser, UserGroup, UserService, auth controllers
```

### Layering Rules

→ See [docs/ARCHITECTURE.md §Layering rule](./docs/ARCHITECTURE.md#layering-rule)

**LLM reminder:** controllers never call repositories directly; services never reach into another domain's repository — always call the other domain's service instead.

### Domain Model

| Entity      | Table         | Key relationships                                                                     |
| ----------- | ------------- | ------------------------------------------------------------------------------------- |
| `Document`  | `documents`   | ManyToOne `sender` (Person), ManyToMany `receivers` (Person), ManyToMany `tags` (Tag) |
| `Person`    | `persons`     | Referenced by documents as sender/receiver                                            |
| `Tag`       | `tag`         | ManyToMany with documents via `document_tags`                                         |
| `AppUser`   | `app_users`   | ManyToMany `groups` (UserGroup)                                                       |
| `UserGroup` | `user_groups` | Has a `Set<String> permissions`                                                       |

**`DocumentStatus` lifecycle:** `PLACEHOLDER → UPLOADED → TRANSCRIBED → REVIEWED → ARCHIVED`

- `PLACEHOLDER`: created during Excel import, no file yet
- `UPLOADED`: file has been stored in S3

### Entity Code Style

All entities use these Lombok annotations:

```java
@Entity
@Table(name = "table_name")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Schema(requiredMode = Schema.RequiredMode.REQUIRED)  // marks field as required in OpenAPI spec
    private UUID id;
    // ...
}
```

- `@Schema(requiredMode = REQUIRED)` must be added to every field the backend always populates (id, non-null fields). This drives the TypeScript type generation.
- Collections use `@Builder.Default` with `new HashSet<>()` as the default.
- Timestamps use `@CreationTimestamp` / `@UpdateTimestamp`.

### Services

Services are annotated with `@Service`, `@RequiredArgsConstructor`, and optionally `@Slf4j`.

- Write methods are annotated `@Transactional`.
- Read methods are not annotated (default non-transactional is fine).
- Each service owns its domain's repository. Cross-domain data access goes through the other domain's service.

### DTOs

Input DTOs live flat in the domain package. Response types are the model entities themselves (no response DTOs).

- `@Schema(requiredMode = REQUIRED)` on every field the backend always populates — drives TypeScript generation.

### Error Handling

→ See [CONTRIBUTING.md §Error handling](./CONTRIBUTING.md#error-handling)

**LLM reminder:** use `DomainException.notFound/forbidden/conflict/internal()` from service methods — never throw raw exceptions. When adding a new `ErrorCode`: (1) add to `ErrorCode.java`, (2) add to `ErrorCode` type in `frontend/src/lib/shared/errors.ts`, (3) add a `case` in `getErrorMessage()`, (4) add i18n keys in `messages/{de,en,es}.json`.

### Security / Permissions

→ See [docs/ARCHITECTURE.md §Permission system](./docs/ARCHITECTURE.md#permission-system)

**LLM reminder:** `@RequirePermission(Permission.WRITE_ALL)` is **required** on every `POST`, `PUT`, `PATCH`, `DELETE` endpoint — not optional. Do not mix with Spring Security's `@PreAuthorize`. Available permissions: `READ_ALL`, `WRITE_ALL`, `ADMIN`, `ADMIN_USER`, `ADMIN_TAG`, `ADMIN_PERMISSION`, `ANNOTATE_ALL`, `BLOG_WRITE`.

### OpenAPI / API Types

→ See [CONTRIBUTING.md §Walkthrough B — Add a new endpoint](./CONTRIBUTING.md#4-walkthrough-b--add-a-new-endpoint)

**LLM reminder:** always run `npm run generate:api` in `frontend/` after any backend model or endpoint change — this is the most common cause of TypeScript type errors.

---

## Frontend Architecture

### Route Structure

```
frontend/src/routes/
├── +layout.svelte / +layout.server.ts   Global layout, auth cookie
├── +page.svelte / +page.server.ts       Home / document search dashboard
├── documents/
│   ├── [id]/               Document detail (view + file preview)
│   ├── [id]/edit/          Edit form (all metadata + file upload)
│   ├── new/                Upload form
│   └── bulk-edit/          Multi-document edit
├── persons/
│   ├── [id]/               Person detail
│   ├── [id]/edit/          Person edit form
│   └── new/                Create person form
├── briefwechsel/           Bilateral conversation timeline (Briefwechsel)
├── aktivitaeten/           Unified activity feed (Chronik)
├── geschichten/            Stories — list, [id], [id]/edit, new
├── stammbaum/              Family tree (Stammbaum)
├── enrich/                 Enrichment workflow — [id], done
├── admin/                  User, group, tag, OCR, system management
├── hilfe/transkription/    Transcription help page
├── profile/                User profile settings
├── users/[id]/             Public user profile page
├── login/ logout/ register/
└── forgot-password/ reset-password/
```

### API Client Pattern

→ See [CONTRIBUTING.md §Frontend API client](./CONTRIBUTING.md#frontend-api-client)

**LLM reminder:** check `!result.response.ok` (not `result.error` — breaks when spec has no error responses defined); cast errors as `result.error as unknown as { code?: string }`; use `result.data!` after an ok check.

### 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

→ See [CONTRIBUTING.md §Date handling](./CONTRIBUTING.md#date-handling)

**LLM reminder:** always append `T12:00:00` when constructing `new Date()` from an ISO date string — prevents UTC timezone off-by-one errors.

### UI Component Library

→ See per-domain READMEs: [`frontend/src/lib/person/README.md`](./frontend/src/lib/person/README.md), [`frontend/src/lib/tag/README.md`](./frontend/src/lib/tag/README.md), [`frontend/src/lib/document/README.md`](./frontend/src/lib/document/README.md), [`frontend/src/lib/shared/README.md`](./frontend/src/lib/shared/README.md)

### Styling Conventions (Tailwind CSS 4)

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

| Token / Utility  | CSS variable     | Usage                                                   |
| ---------------- | ---------------- | ------------------------------------------------------- |
| `brand-navy`     | `--palette-navy` | Tailwind utility — buttons, headers, primary text       |
| `brand-mint`     | `--palette-mint` | Tailwind utility — accents, hover underlines, icons     |
| `--palette-sand` | `--palette-sand` | Palette constant only — use `bg-canvas` or `bg-surface` |

Typography:

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

Card pattern for content sections:

```svelte
<div class="rounded-sm border border-line bg-surface shadow-sm p-6">
    <h2 class="text-xs font-bold uppercase tracking-widest text-ink-3 mb-5">Section Title</h2>
    <!-- content -->
</div>
```

Back button pattern — use the shared `<BackButton>` component from `$lib/shared/primitives/BackButton.svelte`. Do not use a static `<a href>` for back navigation.

### Error Handling (Frontend)

→ See [CONTRIBUTING.md §Error handling](./CONTRIBUTING.md#error-handling)

**LLM reminder:** when adding a new `ErrorCode`: (1) add to `ErrorCode.java`, (2) add to `ErrorCode` type in `frontend/src/lib/shared/errors.ts`, (3) add a `case` in `getErrorMessage()`, (4) add i18n keys in `messages/{de,en,es}.json`.

---

## Infrastructure

→ See [docs/DEPLOYMENT.md](./docs/DEPLOYMENT.md)

### Observability stack (separate compose file)

Run via `docker-compose.observability.yml` — requires the main stack to be running first. Full setup procedure: [docs/DEPLOYMENT.md §4](./docs/DEPLOYMENT.md#4-logs--observability).

| Service | Container | Default Port | Purpose |
|---------|-----------|-------------|---------|
| Grafana | `obs-grafana` | 3003 | Metrics / logs / traces dashboard |
| Prometheus | `obs-prometheus` | 9090 (dev only — `127.0.0.1` bound) | Metrics store |
| Loki | `obs-loki` | — (internal) | Log store |
| Tempo | `obs-tempo` | — (internal) | Trace store |
| GlitchTip | `obs-glitchtip` | 3002 | Error tracking (Sentry-compatible) |

### Observability env vars

| Variable | Purpose |
|----------|---------|
| `PORT_GRAFANA` | Host port for Grafana UI (default: `3003`) |
| `PORT_GLITCHTIP` | Host port for GlitchTip UI (default: `3002`) |
| `PORT_PROMETHEUS` | Host port for Prometheus UI (default: `9090`) |
| `GRAFANA_ADMIN_PASSWORD` | Grafana `admin` login password — generate with `openssl rand -hex 32` |
| `GLITCHTIP_SECRET_KEY` | Django secret key for GlitchTip — generate with `python3 -c "import secrets; print(secrets.token_hex(32))"` |
| `GLITCHTIP_DOMAIN` | Public-facing base URL for GlitchTip (email links, CORS), e.g. `https://glitchtip.example.com` |
| `SENTRY_DSN` | GlitchTip/Sentry DSN for the backend (Spring Boot) — leave empty to disable |
| `VITE_SENTRY_DSN` | GlitchTip/Sentry DSN for the frontend (SvelteKit) — injected at build time via Vite |

## Observability

→ See [docs/OBSERVABILITY.md](./docs/OBSERVABILITY.md) — where to look for logs, traces, metrics, and errors.

## API Testing

HTTP test files are in `backend/api_tests/` for use with the VS Code REST Client extension.

## Dev Container

→ See [.devcontainer/README.md](./.devcontainer/README.md)
