# Familienarchiv — C4 Architecture Diagrams

> For domain terminology used in these diagrams, see [docs/GLOSSARY.md](../GLOSSARY.md).
>
> **Cross-diagram stubs:** Components placed outside a `System_Boundary` block with a "See diagram X" annotation are reference stubs — they represent a component fully defined in another sub-diagram and appear here only to show the cross-domain dependency without duplicating the full definition.

## Level 1 — System Context

Who uses the system and what external systems does it interact with.

```mermaid
C4Context
    title System Context: Familienarchiv

    Person(admin, "Administrator", "Manages users, triggers bulk imports, reviews and transcribes documents")
    Person(member, "Family Member", "Access by administrator invite. Searches, browses, reads, and transcribes archived documents.")

    System(familienarchiv, "Familienarchiv", "Web application for digitising, organising, and searching family documents")
    System_Ext(mail, "Email Service", "SMTP server. Delivers notification emails (mentions, replies) and password-reset links.")

    Rel(admin, familienarchiv, "Manages via browser", "HTTPS")
    Rel(member, familienarchiv, "Searches, reads, and transcribes via browser", "HTTPS")
    Rel(familienarchiv, mail, "Sends notification and password-reset emails (optional)", "SMTP")
```

---

## Level 2 — Containers

The deployable units that make up the system and how they communicate.

```mermaid
C4Container
    title Container Diagram: Familienarchiv

    Person(user, "User", "Admin or family member")
    System_Ext(mail, "Email Service", "SMTP server. Delivers notification and password-reset emails.")

    System_Boundary(archiv, "Familienarchiv (Docker Compose)") {
        Container(frontend, "Web Frontend", "SvelteKit / Node.js", "Server-side rendered UI. Handles auth session cookies, document search and viewer, transcription editor, annotation layer, family tree (Stammbaum), stories (Geschichten), activity feed (Chronik), enrichment workflow, and admin panel.")

        Container(backend, "API Backend", "Spring Boot 4 / Java 21 / Jetty", "REST API. Implements document management, search, user auth, file upload/download, transcription, OCR orchestration, and SSE notifications.")

        Container(ocr, "OCR Service", "Python FastAPI / port 8000", "Handwritten text recognition (HTR) and OCR microservice. Single-node by design — see ADR-001. Reachable only on the internal Docker network; no external port exposed.")

        ContainerDb(db, "Relational Database", "PostgreSQL 16", "Stores document metadata, persons, users, permission groups, tags, transcription blocks, audit log, and Spring Session data.")

        ContainerDb(storage, "Object Storage", "MinIO (S3-compatible)", "Stores the actual document files (PDFs, scans). Objects keyed as documents/{UUID}_{filename}.")

        Container(mc, "Bucket Init Helper", "MinIO Client (mc)", "One-shot container on startup. Creates the archive bucket with private access policy.")
    }

    Rel(user, frontend, "Uses", "HTTPS / Browser")
    Rel(frontend, backend, "API requests with Basic Auth token", "HTTP / REST / JSON")
    Rel(backend, user, "SSE notifications (server-sent events)", "HTTP / SSE — direct backend-to-browser")
    Rel(backend, db, "Reads and writes metadata and sessions", "JDBC / SQL")
    Rel(backend, storage, "Uploads and streams document files", "HTTP / S3 API (AWS SDK v2)")
    Rel(backend, ocr, "OCR job requests with presigned MinIO URL", "HTTP / REST / JSON")
    Rel(backend, mail, "Sends notification and password-reset emails (optional)", "SMTP")
    Rel(ocr, storage, "Fetches PDF via presigned URL", "HTTP / S3 presigned")
    Rel(mc, storage, "Creates bucket on startup", "MinIO Client CLI")
```

---

## Level 3 — Components: API Backend

The internal structure of the Spring Boot backend, split into seven focused sub-diagrams.

### 3a — Security & Authentication

How requests are authenticated and write operations are authorised.

```mermaid
C4Component
    title Component Diagram: API Backend — Security & Authentication

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(secFilter, "Security Filter Chain", "Spring Security", "Enforces authentication on all requests. Parses Basic Auth header and constructs an Authentication token; delegates credential validation to DaoAuthenticationProvider via BCrypt. Permits password-reset, invite, and register endpoints without authentication.")
        Component(permAspect, "PermissionAspect", "Spring AOP", "Intercepts methods annotated with @RequirePermission. Checks user's granted authorities against the required permission. Throws 401/403 if denied.")
        Component(secConf, "SecurityConfig", "Spring @Configuration", "Configures filter chain: all routes require authentication, CSRF disabled, BCrypt password encoder, DaoAuthenticationProvider with CustomUserDetailsService.")
        Component(userDetails, "CustomUserDetailsService", "Spring Security UserDetailsService", "Loads AppUser by email from DB. Converts group permissions to Spring GrantedAuthority objects. Logs unknown permissions.")
    }

    Rel(frontend, secFilter, "All requests", "HTTP / Basic Auth header")
    Rel(secFilter, permAspect, "Authenticated requests reach guarded service methods", "")
    Rel(secConf, userDetails, "Wires as UserDetailsService", "")
    Rel(userDetails, db, "Loads user by email", "JDBC")
```

### 3b — Document Management & Import

Document management, file storage, and the canonical import.

```mermaid
C4Component
    title Component Diagram: API Backend — Document Management & Import

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")
    ContainerDb(minio, "MinIO")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(docCtrl, "DocumentController", "Spring MVC — /api/documents", "CRUD for documents: search, get by ID, update metadata, upload/download file, and batch metadata updates.")
        Component(adminCtrl, "AdminController", "Spring MVC — /api/admin", "Triggers the asynchronous canonical import (requires ADMIN permission). Reports import state via GET /api/admin/import-status (IDLE/RUNNING/DONE/FAILED).")

        Component(docSvc, "DocumentService", "Spring Service", "Core document business logic: store, update, search. Resolves persons and tags, delegates file I/O to FileService, builds dynamic JPA Specifications, and integrates with audit logging.")
        Component(fileSvc, "FileService", "Spring Service", "Wraps AWS SDK v2 S3Client. Uploads files with UUID-keyed paths, computes SHA-256 hash, downloads with content-type detection, and generates presigned URLs for OCR access.")
        Component(importOrch, "CanonicalImportOrchestrator", "Spring Service — @Async", "Runs four idempotent loaders (TagTree → PersonRegister → PersonTree → Document) in a fixed DAG over the normalizer's committed canonical artifacts (canonical-*.xlsx + canonical-persons-tree.json) from /import — see diagram 3b. Owns the IDLE/RUNNING/DONE/FAILED state machine.")
        Component(minioConf, "MinioConfig", "Spring @Configuration", "Creates the S3Client and S3Presigner beans with path-style access for MinIO. Validates MinIO connectivity on startup.")

        Component(docRepo, "DocumentRepository", "Spring Data JPA", "Queries documents with Specification-based dynamic search, full-text search with ranking and match highlighting, and transcription pipeline queue projections.")
        Component(docSpec, "DocumentSpecifications", "JPA Criteria API", "Factory for composable predicates: hasText (full-text), hasSender, hasReceiver, isBetween (date range), hasTags (subquery AND/OR logic).")
    }

    Component(personSvc, "PersonService", "Spring Service", "See diagram 3e. Called by DocumentService to resolve sender / receiver persons by ID.")
    Component(tagSvc, "TagService", "Spring Service", "See diagram 3d. Called by DocumentService to find or create tags by name.")

    Rel(frontend, docCtrl, "Document requests", "HTTP / JSON")
    Rel(frontend, adminCtrl, "Trigger import", "HTTP / JSON")
    Rel(docCtrl, docSvc, "Delegates to", "")
    Rel(adminCtrl, importOrch, "Triggers", "")
    Rel(docSvc, fileSvc, "Upload / download files", "")
    Rel(docSvc, docRepo, "Reads / writes documents", "")
    Rel(docSvc, docSpec, "Builds search predicates", "")
    Rel(docSvc, personSvc, "Resolves sender / receivers", "")
    Rel(docSvc, tagSvc, "Finds or creates tags", "")
    Rel(importOrch, docSvc, "Upserts documents (PDF by index) — see 3b", "")
    Rel(importOrch, personSvc, "Upserts persons + relationships", "")
    Rel(importOrch, tagSvc, "Upserts tag hierarchy", "")
    Rel(minioConf, fileSvc, "Provides S3Client and S3Presigner beans", "")
    Rel(fileSvc, minio, "PUT / GET / presigned URL objects", "S3 API / HTTP")
    Rel(docRepo, db, "SQL queries", "JDBC")
```

### 3c — Document Transcription Pipeline

Annotation-driven transcription: page markup, text blocks, versioning, and comment threads.

```mermaid
C4Component
    title Component Diagram: API Backend — Document Transcription Pipeline

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(transcriptionCtrl, "TranscriptionBlockController", "Spring MVC — /api/transcription", "CRUD for transcription text blocks per document page. Manages sort order, review status, and block version history.")
        Component(annotationCtrl, "AnnotationController", "Spring MVC — /api/documents/{id}/annotations", "CRUD for free-form page annotations with polygon coordinates, colour coding, and file-hash tracking.")
        Component(commentCtrl, "CommentController", "Spring MVC — /api/documents/{id}/comments", "Threaded comment CRUD on transcription blocks with @mention support and notification triggers.")

        Component(transcriptionSvc, "TranscriptionService", "Spring Service", "Creates and updates transcription blocks from annotation regions. Tracks block versions, sanitizes text with an HTML allow-list, and triggers mentions.")
        Component(transcriptionQueueSvc, "TranscriptionQueueService", "Spring Service", "Assembles segmentation, transcription, and review queue projections by delegating to DocumentService and AuditLogQueryService.")
        Component(annotationSvc, "AnnotationService", "Spring Service", "Manages document page annotations with polygon coordinates. Called by OcrAsyncRunner to persist OCR-generated block boundaries.")
        Component(commentSvc, "CommentService", "Spring Service", "Creates and manages threaded comments with @mention parsing. Triggers NotificationService for REPLY and MENTION events.")

        Component(blockRepo, "TranscriptionBlockRepository", "Spring Data JPA", "Reads and writes TranscriptionBlock and TranscriptionBlockVersion records.")
        Component(annotationRepo, "AnnotationRepository", "Spring Data JPA", "Reads and writes DocumentAnnotation records.")
        Component(commentRepo, "CommentRepository", "Spring Data JPA", "Reads and writes DocumentComment records.")
    }

    Component(documentSvc, "DocumentService", "Spring Service", "See diagram 3b. Called by TranscriptionQueueService to assemble pipeline queue projections.")
    Component(auditQuerySvc, "AuditLogQueryService", "Spring Service", "See diagram 3g. Called by TranscriptionQueueService for pipeline activity data.")

    Rel(frontend, transcriptionCtrl, "Transcription block requests", "HTTP / JSON")
    Rel(frontend, annotationCtrl, "Annotation requests", "HTTP / JSON")
    Rel(frontend, commentCtrl, "Comment requests", "HTTP / JSON")
    Rel(transcriptionCtrl, transcriptionSvc, "Delegates to", "")
    Rel(transcriptionCtrl, transcriptionQueueSvc, "Queries pipeline queues", "")
    Rel(annotationCtrl, annotationSvc, "Delegates to", "")
    Rel(commentCtrl, commentSvc, "Delegates to", "")
    Rel(transcriptionSvc, blockRepo, "Reads / writes blocks and versions", "")
    Rel(annotationSvc, annotationRepo, "Reads / writes annotations", "")
    Rel(commentSvc, commentRepo, "Reads / writes comments", "")
    Rel(transcriptionQueueSvc, documentSvc, "Queries pipeline document state", "")
    Rel(transcriptionQueueSvc, auditQuerySvc, "Queries pipeline activity data", "")
    Rel(blockRepo, db, "SQL queries", "JDBC")
    Rel(annotationRepo, db, "SQL queries", "JDBC")
    Rel(commentRepo, db, "SQL queries", "JDBC")
```

### 3d — Users, Groups & Administration

User lifecycle, permission groups, tag management, and authentication endpoints.

```mermaid
C4Component
    title Component Diagram: API Backend — Users, Groups & Administration

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(userCtrl, "UserController", "Spring MVC — /api/users", "Returns current user (/me), creates and deletes users (requires ADMIN_USER), supports user search and profile updates.")
        Component(groupCtrl, "GroupController", "Spring MVC — /api/groups", "Lists and manages permission groups.")
        Component(tagCtrl, "TagController", "Spring MVC — /api/tags", "Lists tags for typeahead, supports tag merge, tree structure, and subtree deletion.")
        Component(inviteCtrl, "InviteController", "Spring MVC — /api/auth/invite", "Creates invite codes and validates them at registration time. Rate-limited via WebConfig interceptor.")
        Component(authCtrl, "AuthController", "Spring MVC — /api/auth", "Handles user registration (POST /register) and password reset token endpoints (/forgot-password, /reset-password).")

        Component(userSvc, "UserService", "Spring Service", "User CRUD with BCrypt password encoding, group assignment, and audit logging. Orchestrates invite-based registration and password reset tokens.")
        Component(tagSvc, "TagService", "Spring Service", "Tag CRUD with name search, hierarchical tree structure, merge/reparent operations, and recursive subtree deletion.")
        Component(dataInit, "DataInitializer", "CommandLineRunner", "On startup: creates default admin user and groups if none exist. Seeds test data if DB is empty.")

        Component(userRepo, "AppUserRepository", "Spring Data JPA", "Finds users by email. Supports search by email or display name.")
        Component(groupRepo, "UserGroupRepository", "Spring Data JPA", "Manages permission groups.")
        Component(tagRepo, "TagRepository", "Spring Data JPA", "Finds or creates tags by name (case-insensitive). Supports recursive ancestor/descendant CTE queries and merge/reparent helpers.")
    }

    Rel(frontend, userCtrl, "User requests", "HTTP / JSON")
    Rel(frontend, groupCtrl, "Group requests", "HTTP / JSON")
    Rel(frontend, tagCtrl, "Tag requests", "HTTP / JSON")
    Rel(frontend, inviteCtrl, "Invite validation", "HTTP / JSON")
    Rel(frontend, authCtrl, "Registration and password reset", "HTTP / JSON")
    Rel(userCtrl, userSvc, "Delegates to", "")
    Rel(groupCtrl, userSvc, "Delegates to", "")
    Rel(tagCtrl, tagSvc, "Delegates to", "")
    Rel(tagSvc, tagRepo, "Reads / writes tags", "")
    Rel(inviteCtrl, userSvc, "Creates and validates invites", "")
    Rel(authCtrl, userSvc, "Registers users, resets passwords", "")
    Rel(userSvc, userRepo, "Reads / writes users", "")
    Rel(userSvc, groupRepo, "Assigns groups", "")
    Rel(dataInit, db, "Seeds initial data", "JDBC")
    Rel(userRepo, db, "SQL queries", "JDBC")
    Rel(groupRepo, db, "SQL queries", "JDBC")
    Rel(tagRepo, db, "SQL queries", "JDBC")
```

### 3e — Persons & Family Graph

Person management including family relationship modelling and transitive inference.

```mermaid
C4Component
    title Component Diagram: API Backend — Persons & Family Graph

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(personCtrl, "PersonController", "Spring MVC — /api/persons", "Lists and searches family members. Returns documents sent by or received by a person, correspondent suggestions, and person summary with document counts.")
        Component(relCtrl, "RelationshipController", "Spring MVC — /api/network, /api/persons/{id}/relationships", "CRUD for explicit person relationships and the full family network graph (nodes + edges) used by the Stammbaum view.")

        Component(personSvc, "PersonService", "Spring Service", "Person CRUD, alias management, and merge operations (reassigns all document sender/receiver references before deleting duplicate persons).")
        Component(relSvc, "RelationshipService", "Spring Service", "Manages explicit directional family relationships (PARENT_OF, SPOUSE_OF, SIBLING_OF, etc.) with optional date ranges and notes.")
        Component(relInference, "RelationshipInferenceService", "Spring Service", "Computes transitive family relationships from explicit edges to infer grandparent/grandchild, aunt/uncle, and other extended-family links for the network graph.")

        Component(personRepo, "PersonRepository", "Spring Data JPA", "Queries persons with name search (including aliases), correspondent discovery, person summaries with document counts, and merge/reassignment helpers.")
        Component(relRepo, "PersonRelationshipRepository", "Spring Data JPA", "Reads and writes PersonRelationship records. Supports lookup by person ID, by relation type, and existence checks for deduplication.")
    }

    Rel(frontend, personCtrl, "Person requests", "HTTP / JSON")
    Rel(frontend, relCtrl, "Relationship and graph requests", "HTTP / JSON")
    Rel(personCtrl, personSvc, "Delegates to", "")
    Rel(relCtrl, relSvc, "Delegates to", "")
    Rel(relCtrl, relInference, "Queries inferred graph", "")
    Rel(personSvc, personRepo, "Reads / writes persons", "")
    Rel(relSvc, relRepo, "Reads / writes relationships", "")
    Rel(relInference, relRepo, "Reads relationships for inference", "")
    Rel(personRepo, db, "SQL queries", "JDBC")
    Rel(relRepo, db, "SQL queries", "JDBC")
```

### 3f — OCR Orchestration

How the Spring Boot backend manages OCR jobs, streams results, and trains recognition models.

```mermaid
C4Component
    title Component Diagram: API Backend — OCR Orchestration

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")
    ContainerDb(minio, "MinIO")
    Container(ocrPy, "OCR Service", "Python FastAPI")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(ocrCtrl, "OcrController", "Spring MVC — /api/ocr", "REST entry point: trigger single or batch OCR jobs, stream progress via SSE, query job status, and manage training runs and per-sender models.")
        Component(ocrSvc, "OcrService", "Spring Service", "Creates OcrJob and OcrJobDocument records, checks Python service health, and delegates async execution to OcrAsyncRunner.")
        Component(ocrBatch, "OcrBatchService", "Spring Service", "Orchestrates multi-document OCR jobs, iterating documents and delegating each to OcrAsyncRunner.")
        Component(ocrAsync, "OcrAsyncRunner", "Spring Component — @Async", "Async worker that streams OCR results from Python page by page, persists transcription blocks and annotations via domain services, and emits progress via SSE.")
        Component(ocrClient, "RestClientOcrClient", "Spring Component", "HTTP client wrapping the Python service: POST /ocr/stream (NDJSON), /train, /segtrain, and /train-sender. Falls back from streaming to batch on 404.")
        Component(ocrTraining, "OcrTrainingService", "Spring Service", "Orchestrates model training: exports training data as ZIP, calls Python /train or /segtrain, persists training metrics in OcrTrainingRunRepository.")
        Component(ocrJobRepo, "OcrJobRepository, OcrJobDocumentRepository", "Spring Data JPA", "Reads and writes OcrJob and OcrJobDocument records. Tracks job status (RUNNING/DONE/FAILED), per-document progress, page counts, and error messages.")
    }

    Component(transcriptionSvc, "TranscriptionService", "Spring Service", "See diagram 3c. Called by OcrAsyncRunner to persist transcription blocks per page.")
    Component(annotationSvc, "AnnotationService", "Spring Service", "See diagram 3c. Called by OcrAsyncRunner to persist OCR-generated annotation regions per page.")

    Rel(frontend, ocrCtrl, "OCR trigger, status, and progress requests", "HTTP / JSON / SSE")
    Rel(ocrCtrl, ocrSvc, "Single-document jobs", "")
    Rel(ocrCtrl, ocrBatch, "Batch jobs", "")
    Rel(ocrCtrl, ocrTraining, "Training runs", "")
    Rel(ocrSvc, ocrAsync, "Delegates async execution", "")
    Rel(ocrBatch, ocrAsync, "Delegates async execution", "")
    Rel(ocrAsync, ocrClient, "Streams OCR results page by page", "HTTP / NDJSON")
    Rel(ocrTraining, ocrClient, "Sends training data ZIP", "HTTP / multipart")
    Rel(ocrClient, ocrPy, "POST /ocr/stream, /train, /segtrain, /train-sender", "HTTP / REST")
    Rel(ocrAsync, transcriptionSvc, "Saves transcription blocks per page", "")
    Rel(ocrAsync, annotationSvc, "Saves annotation regions per page", "")
    Rel(ocrAsync, ocrJobRepo, "Reads / writes OCR job state", "")
    Rel(ocrJobRepo, db, "SQL queries", "JDBC")
    Rel(ocrAsync, minio, "Generates presigned URLs for PDF fetch", "S3 API")
    Rel(ocrPy, minio, "Fetches PDF via presigned URL", "HTTP / S3 presigned")
    Rel(ocrTraining, db, "Persists training run metrics", "JDBC")
```

### 3g — Supporting Domains

Audit logging, dashboard stats, SSE notifications, stories (Geschichten), and cross-cutting exception handling.

```mermaid
C4Component
    title Component Diagram: API Backend — Supporting Domains

    Container(frontend, "Web Frontend", "SvelteKit")
    ContainerDb(db, "PostgreSQL")

    System_Boundary(backend, "API Backend (Spring Boot)") {
        Component(auditSvc, "AuditService", "Spring Service — @Async", "Writes audit log entries asynchronously via a dedicated TaskExecutor, with transaction-aware logging to prevent deadlocks on concurrent saves.")
        Component(auditQuery, "AuditLogQueryService", "Spring Service", "Queries audit logs for activity feeds, pulse stats, recent contributors, and per-document history. Facade over AuditLogRepository.")

        Component(dashCtrl, "DashboardController", "Spring MVC — /api/dashboard", "REST endpoints for the user dashboard: recent document resume (/resume), weekly transcription pulse stats (/pulse), and activity feed (/activity) with kind filtering and pagination.")
        Component(statsCtrl, "StatsController", "Spring MVC — /api/stats", "Returns aggregate counts (total persons, total documents) for the UI stats bar.")
        Component(statsSvc, "StatsService", "Spring Service", "Queries aggregate counts: total persons and total documents.")
        Component(dashSvc, "DashboardService", "Spring Service", "Assembles the user dashboard: recent document resume (calls DocumentService + TranscriptionService), weekly transcription pulse stats, and activity feed with contributor avatars.")

        Component(notifCtrl, "NotificationController", "Spring MVC — /api/notifications", "REST and SSE endpoints for notification stream, history with filtering, read/unread state, and per-user preference management.")
        Component(notifSvc, "NotificationService", "Spring Service", "Creates REPLY and MENTION notifications, optionally sends email, marks as read, and pushes events to connected clients via SseEmitterRegistry.")
        Component(sseRegistry, "SseEmitterRegistry", "Spring Component", "In-memory ConcurrentHashMap of Spring SseEmitter instances per user. Handles registration, deregistration, and JSON event broadcasts.")

        Component(geschCtrl, "GeschichteController", "Spring MVC — /api/geschichten", "CRUD for publishable stories that link persons and documents. Requires BLOG_WRITE permission for write operations.")
        Component(geschSvc, "GeschichteService", "Spring Service", "Manages story lifecycle (DRAFT → PUBLISHED with timestamp). Sanitizes HTML body with an allowlist policy.")

        Component(exHandler, "GlobalExceptionHandler", "Spring @RestControllerAdvice", "Converts DomainException, validation errors, and generic exceptions to ErrorResponse JSON with machine-readable ErrorCode and HTTP status.")
    }

    Component(documentSvc, "DocumentService", "Spring Service", "See diagram 3b. Called by DashboardService to fetch document titles and resume data.")
    Component(transcriptionSvc, "TranscriptionService", "Spring Service", "See diagram 3c. Called by DashboardService to fetch transcription block progress for resume.")

    Rel(frontend, dashCtrl, "Dashboard requests", "HTTP / JSON")
    Rel(frontend, statsCtrl, "GET /api/stats", "HTTP / JSON")
    Rel(frontend, notifCtrl, "Notification stream and history", "HTTP / JSON / SSE")
    Rel(frontend, geschCtrl, "Story requests", "HTTP / JSON")
    Rel(dashCtrl, dashSvc, "Delegates to", "")
    Rel(statsCtrl, statsSvc, "Delegates to", "")
    Rel(statsSvc, db, "Reads aggregate counts", "JDBC")
    Rel(dashSvc, auditQuery, "Fetches activity feed and pulse stats", "")
    Rel(dashSvc, documentSvc, "Fetches document titles and resume data", "")
    Rel(dashSvc, transcriptionSvc, "Fetches transcription block progress for resume", "")
    Rel(notifCtrl, notifSvc, "Delegates to", "")
    Rel(notifCtrl, sseRegistry, "Registers client SSE connection", "")
    Rel(notifSvc, sseRegistry, "Broadcasts events to connected clients", "")
    Rel(geschCtrl, geschSvc, "Delegates to", "")
    Rel(auditSvc, db, "Writes audit_log", "JDBC")
    Rel(auditQuery, db, "Reads audit_log", "JDBC")
    Rel(notifSvc, db, "Reads / writes notifications", "JDBC")
    Rel(geschSvc, db, "Reads / writes geschichten", "JDBC")
```

---

## Level 3 — Components: Web Frontend

The internal structure of the SvelteKit frontend, split into four focused views.

### 3a — Middleware, Auth & Layout

Per-request middleware: session validation, i18n, auth cookie handling, and auth pages.

```mermaid
C4Component
    title Component Diagram: Web Frontend — Middleware, Auth & Layout

    Person(user, "User")
    Container(backend, "API Backend", "Spring Boot")

    System_Boundary(frontend, "Web Frontend (SvelteKit / SSR)") {
        Component(hooks, "hooks.server.ts", "SvelteKit Server Hook", "Four handle layers: (1) handleAuth — redirects unauthenticated users to /login; (2) userGroup — reads auth_token cookie, fetches /api/users/me, stores user in event.locals; (3) handleFetch — injects Authorization header on all outgoing /api/ calls; (4) handleLocaleDetection — sets language cookie from Accept-Language header.")
        Component(i18n, "hooks.ts (Paraglide)", "SvelteKit Client Hook", "Client-side i18n middleware. Detects language from URL and sets the active locale for Paraglide.js translation functions.")
        Component(layout, "+layout.server.ts", "SvelteKit Layout Loader", "Passes event.locals.user down to all child pages so every route has access to the authenticated user.")
        Component(loginPage, "/login", "SvelteKit Route", "Form action: encodes email:password as Base64 Basic Auth token, POSTs to /api/users/me to validate, sets auth_token httpOnly cookie (SameSite=strict, maxAge=86400), redirects to /.")
        Component(logoutPage, "/logout", "SvelteKit Route (server-only)", "Clears the auth_token cookie and redirects to /login.")
        Component(registerPage, "/register", "SvelteKit Route", "Loader validates invite code via GET /api/auth/invite/{code}. Form action: POST /api/auth/register to create the user account.")
        Component(forgotPw, "/forgot-password", "SvelteKit Route", "Form action: POST /api/auth/forgot-password. Always responds with success to prevent email enumeration.")
        Component(resetPw, "/reset-password", "SvelteKit Route", "Form action: POST /api/auth/reset-password with the token from the query string.")
    }

    Rel(user, hooks, "Every browser request", "HTTPS")
    Rel(hooks, backend, "GET /api/users/me (session check)", "HTTP / Basic Auth")
    Rel(hooks, loginPage, "Redirect if no token", "")
    Rel(hooks, layout, "Stores authenticated user in event.locals", "")
    Rel(loginPage, backend, "POST /api/users/me (auth check)", "HTTP / Basic Auth")
    Rel(registerPage, backend, "GET /api/auth/invite/{code}, POST /api/auth/register", "HTTP / JSON")
    Rel(forgotPw, backend, "POST /api/auth/forgot-password", "HTTP / JSON")
    Rel(resetPw, backend, "POST /api/auth/reset-password", "HTTP / JSON")
```

### 3b — Document Workflows

Document search, viewing, editing, enrichment, and the shared components that support them.

```mermaid
C4Component
    title Component Diagram: Web Frontend — Document Workflows

    Person(user, "User")
    Container(backend, "API Backend", "Spring Boot")

    System_Boundary(frontend, "Web Frontend (SvelteKit / SSR)") {
        Component(homePage, "/ (Home / Search)", "SvelteKit Route", "Loader: parses URL params (q, from, to, senderId, receiverId, tags), fetches /api/documents/search and /api/persons. Renders search form with full-text, date range, sender/receiver typeahead, and tag filters.")
        Component(docDetail, "/documents/[id]", "SvelteKit Route", "Loader: GET /api/documents/{id}. Page: metadata panel, inline file viewer, transcription editor, annotation layer, and comment thread.")
        Component(docEdit, "/documents/[id]/edit", "SvelteKit Route", "Edit form with PersonTypeahead, TagInput, date/location fields. Form action: PUT /api/documents/{id}.")
        Component(docNew, "/documents/new", "SvelteKit Route", "Upload form for a new document. Loader: GET /api/persons. Form action: POST /api/documents with multipart file.")
        Component(docBulkEdit, "/documents/bulk-edit", "SvelteKit Route", "Multi-document metadata editor. Loader: GET /api/documents/incomplete. Requires WRITE_ALL (redirects otherwise). Action: PATCH /api/documents/bulk.")
        Component(enrichPage, "/enrich/[id]", "SvelteKit Route", "Guided enrichment workflow. Loader: GET /api/documents/{id}. Progressively saves annotations and transcription blocks.")

        Component(apiPersons, "/api/persons (SvelteKit API)", "SvelteKit Server Route", "Proxies GET /api/persons?q=... to backend for PersonTypeahead suggestions.")
        Component(apiTags, "/api/tags (SvelteKit API)", "SvelteKit Server Route", "Proxies GET /api/tags to backend for TagInput autocomplete.")
        Component(typeahead, "PersonTypeahead.svelte", "Svelte Component", "Async autocomplete for selecting a person. Debounces input, calls /api/persons?q=.")
        Component(tagInput, "TagInput.svelte", "Svelte Component", "Multi-tag input. Supports free-text entry and selecting existing tags from /api/tags.")
    }

    Rel(user, homePage, "Searches and browses", "HTTPS / Browser")
    Rel(homePage, backend, "GET /api/documents/search, GET /api/persons", "HTTP / JSON")
    Rel(docDetail, backend, "GET /api/documents/{id}, GET /api/documents/{id}/file", "HTTP / JSON + Binary")
    Rel(docEdit, backend, "PUT /api/documents/{id}", "HTTP / Multipart")
    Rel(docNew, backend, "GET /api/persons, POST /api/documents", "HTTP / JSON + Multipart")
    Rel(docBulkEdit, backend, "GET /api/documents/incomplete, PATCH /api/documents/bulk", "HTTP / JSON")
    Rel(enrichPage, backend, "GET/POST /api/transcription, POST /api/documents/{id}/annotations", "HTTP / JSON")
    Rel(homePage, typeahead, "Uses for sender/receiver filter", "")
    Rel(docEdit, typeahead, "Uses for sender/receiver selection", "")
    Rel(docNew, typeahead, "Uses for sender selection", "")
    Rel(docEdit, tagInput, "Uses for tag management", "")
    Rel(typeahead, apiPersons, "Fetches suggestions", "HTTP")
    Rel(tagInput, apiTags, "Fetches existing tags", "HTTP")
    Rel(apiPersons, backend, "GET /api/persons", "HTTP / JSON")
    Rel(apiTags, backend, "GET /api/tags", "HTTP / JSON")
```

### 3c — People, Stories & Discovery

Person directory, activity feed, stories, family tree, and user profiles.

```mermaid
C4Component
    title Component Diagram: Web Frontend — People, Stories & Discovery

    Person(user, "User")
    Container(backend, "API Backend", "Spring Boot")

    System_Boundary(frontend, "Web Frontend (SvelteKit / SSR)") {
        Component(personsPage, "/persons and /persons/[id]", "SvelteKit Routes", "Person directory and detail. Detail: metadata, document list sent/received, correspondents, explicit and inferred family relationships.")
        Component(personEdit, "/persons/[id]/edit and /persons/new", "SvelteKit Routes", "Create and edit person forms. Edit: metadata, aliases, explicit relationships. Actions: PUT/POST /api/persons.")
        Component(aktivitaeten, "/aktivitaeten", "SvelteKit Route", "Unified activity feed (Chronik). Loader: GET /api/dashboard/activity and GET /api/notifications?read=false.")
        Component(geschichten, "/geschichten and /geschichten/[id]", "SvelteKit Routes", "Story list and detail pages. Loader: GET /api/geschichten?status=PUBLISHED.")
        Component(geschichtenEdit, "/geschichten/[id]/edit and /geschichten/new", "SvelteKit Routes", "Story editor with rich text, person and document linking. Actions: PUT/POST /api/geschichten. Requires BLOG_WRITE permission.")
        Component(stammbaum, "/stammbaum", "SvelteKit Route", "Family tree visualisation. Loader: GET /api/network (nodes + edges). Renders interactive family tree from network graph data.")
        Component(profilePage, "/profile", "SvelteKit Route", "Current user profile settings. Loader: GET /api/users/me/notification-preferences. Actions: update name/password and notification preferences.")
        Component(userProfile, "/users/[id]", "SvelteKit Route", "Public user profile view. Loader: GET /api/users/{id}.")
    }

    Rel(user, personsPage, "Browses family members", "HTTPS / Browser")
    Rel(personsPage, backend, "GET /api/persons, GET /api/persons/{id}", "HTTP / JSON")
    Rel(personEdit, backend, "GET /api/persons/{id}, PUT /api/persons/{id}, POST /api/persons", "HTTP / JSON")
    Rel(aktivitaeten, backend, "GET /api/dashboard/activity, GET /api/notifications", "HTTP / JSON")
    Rel(geschichten, backend, "GET /api/geschichten", "HTTP / JSON")
    Rel(geschichtenEdit, backend, "GET/PUT/POST /api/geschichten", "HTTP / JSON")
    Rel(stammbaum, backend, "GET /api/network", "HTTP / JSON")
    Rel(profilePage, backend, "GET/PUT /api/users/me, notification-preferences", "HTTP / JSON")
    Rel(userProfile, backend, "GET /api/users/{id}", "HTTP / JSON")
```

### 3d — Administration & Help

Admin panel sub-routes and the transcription help guide.

```mermaid
C4Component
    title Component Diagram: Web Frontend — Administration & Help

    Person(admin, "Administrator")
    Person(user, "User")
    Container(backend, "API Backend", "Spring Boot")

    System_Boundary(frontend, "Web Frontend (SvelteKit / SSR)") {
        Component(adminUsers, "/admin/users, /admin/users/[id], /admin/users/new, /admin/invites", "SvelteKit Routes", "User directory, create/update/delete users, and manage invite codes. Requires ADMIN_USER permission.")
        Component(adminGroups, "/admin/groups, /admin/groups/[id], /admin/groups/new", "SvelteKit Routes", "Permission group management: create/edit groups and their permission sets.")
        Component(adminTags, "/admin/tags and /admin/tags/[id]", "SvelteKit Routes", "Tag administration: edit tag hierarchy, merge tags, delete subtrees.")
        Component(adminOcr, "/admin/ocr and /admin/ocr/[personId]", "SvelteKit Routes", "Global and per-person OCR configuration. Manages script types and triggers sender model training.")
        Component(adminSystem, "/admin/system", "SvelteKit Route", "System status panel. Triggers the canonical import (POST /api/admin/trigger-import). Displays import state.")
        Component(hilfe, "/hilfe/transkription", "SvelteKit Route", "Static transcription style guide for Kurrent and Sütterlin character recognition. No backend calls.")
    }

    Rel(admin, adminUsers, "Manages users and invites", "HTTPS / Browser")
    Rel(user, hilfe, "Views transcription style guide", "HTTPS / Browser")
    Rel(adminUsers, backend, "GET/POST/DELETE /api/users, POST /api/auth/invite", "HTTP / JSON")
    Rel(adminGroups, backend, "GET/POST/PUT/DELETE /api/groups", "HTTP / JSON")
    Rel(adminTags, backend, "GET/PUT/DELETE /api/tags", "HTTP / JSON")
    Rel(adminOcr, backend, "GET/POST /api/ocr (global config and sender training)", "HTTP / JSON")
    Rel(adminSystem, backend, "POST /api/admin/trigger-import, GET /api/admin/import-status", "HTTP / JSON")
```

---

## Authentication Flow

How a user session is established and maintained across requests.

```mermaid
sequenceDiagram
    actor User
    participant Browser
    participant Frontend as Frontend (SvelteKit)
    participant Backend as Backend (Spring Boot)
    participant DB as PostgreSQL

    User->>Browser: Enter email + password
    Browser->>Frontend: POST /login (form action)
    Frontend->>Frontend: Base64 encode "email:password"
    Frontend->>Backend: GET /api/users/me<br/>Authorization: Basic <token>
    Backend->>Backend: Spring Security parses Basic Auth
    Backend->>DB: SELECT user WHERE email=?
    DB-->>Backend: AppUser + groups + permissions
    Backend->>Backend: BCrypt.matches(password, hash)
    Backend-->>Frontend: 200 OK — UserDTO
    Frontend->>Browser: Set-Cookie: auth_token=<base64><br/>(httpOnly, SameSite=strict, maxAge=86400)
    Browser->>Frontend: GET / (next request)
    Frontend->>Frontend: hooks.server.ts reads auth_token cookie
    Frontend->>Backend: GET /api/users/me<br/>Authorization: Basic <token>
    Backend-->>Frontend: 200 OK — user in event.locals
    Frontend-->>Browser: Render page with user context
```

---

## Document Upload Flow

How a document file moves from a user's browser to MinIO.

```mermaid
sequenceDiagram
    actor User
    participant Frontend as Frontend (SvelteKit)
    participant Backend as Backend (Spring Boot)
    participant Aspect as PermissionAspect (AOP)
    participant DocSvc as DocumentService
    participant FileSvc as FileService
    participant MinIO
    participant DB as PostgreSQL

    User->>Frontend: Submit edit form (file + metadata)
    Frontend->>Backend: PUT /api/documents/{id}<br/>multipart/form-data + Authorization header
    Backend->>Aspect: @RequirePermission(WRITE_ALL) check
    Aspect->>Aspect: Verify user has WRITE_ALL authority
    Aspect-->>Backend: Proceed
    Backend->>DocSvc: updateDocument(id, dto, file)
    DocSvc->>DocSvc: Resolve sender Person by ID
    DocSvc->>DocSvc: Resolve/create Tags
    DocSvc->>FileSvc: uploadFile(file, filename)
    FileSvc->>FileSvc: Generate key: documents/{UUID}_{filename}
    FileSvc->>MinIO: PutObject(bucket, key, stream)
    MinIO-->>FileSvc: Success
    FileSvc-->>DocSvc: S3 key
    DocSvc->>DB: UPDATE documents SET file_path=?, status='UPLOADED', ...
    DB-->>DocSvc: OK
    DocSvc-->>Backend: Updated Document entity
    Backend-->>Frontend: 200 OK — Document JSON
    Frontend-->>User: Refreshed document view
```

---

## Database

Entity-relationship and full column reference for the PostgreSQL schema (30 tables, 7 domain groups). Source files in `docs/architecture/db/`.

- **[db-relationships.puml](db/db-relationships.puml)** — Entity relationships: all tables and foreign-key connections, grouped by domain. Start here for an overview.
- **[db-orm.puml](db/db-orm.puml)** — Full schema reference: all columns and types for all 30 tables. Use this when mapping Java entities to database columns.

> Schema as of Flyway V60 (2026-05-06). Open in VS Code with the PlantUML extension (server: `http://heim-nas:8500`).
