# 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 3000) 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 ``` 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 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) mirror in `frontend/src/lib/shared/errors.ts`, (3) 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 Global header (sticky), nav links, logout ├── +layout.server.ts Loads current user, injects auth cookie ├── +page.svelte Home / document search ├── +page.server.ts Load: search documents; no actions ├── documents/ │ ├── [id]/+page.svelte Document detail (view + file preview) │ └── [id]/edit/ Edit form (all metadata + file upload) │ └── new/ Create form (same fields, empty) ├── persons/ │ ├── +page.svelte Person list with search │ ├── [id]/+page.svelte Person detail (inline edit + merge) │ └── new/ Create person form ├── conversations/ Bilateral conversation timeline ├── admin/ User + group + tag management └── login/ logout/ Auth pages ``` ### 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 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

Section Title

``` Back button pattern — use the shared `` component from `$lib/shared/primitives/BackButton.svelte`. Do not use a static `` 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) ## 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)