# Legal Consent Hub - Project Documentation ## Project Overview **Legal Consent Hub** is a comprehensive platform for digital applications, approvals, and discussions, designed specifically for organizational workflows involving employee data processing and consent management. The platform enables organizations to create, manage, and track application forms with built-in compliance features for GDPR requirements. ### Key Features - **Dynamic Form Management**: Create and modify application forms with flexible, runtime-editable structures - **Template System**: Reusable form templates for common use cases - **Version Control**: Complete version history with snapshots for audit trails - **PDF/HTML Export**: Generate professional PDF and HTML documents from forms - **Collaborative Comments**: Attach comments to specific form elements for discussions - **Smart Notifications**: User-specific, role-based, or organization-wide notifications - **Multi-tenancy**: Organization-based data isolation - **GDPR Compliance**: Built-in processing purposes and employee data categorization --- ## Architecture ### System Components The application follows a **three-tier architecture**: 1. **Frontend** (`legalconsenthub/`): Nuxt 4 SSR application 2. **Backend** (`legalconsenthub-backend/`): Spring Boot 3 REST API with Kotlin 3. **Middleware** (`legalconsenthub-middleware/`): Client middleware layer (referenced but minimal presence in codebase) ### Technology Stack #### Frontend - **Framework**: Nuxt 4.2.0 with Vue 3 (latest) - **UI Library**: Nuxt UI 4.1.0 (Tailwind-based component library) - **State Management**: Pinia 3.0.3 - **Internationalization**: @nuxtjs/i18n 10.0.3 (default: German, supports English) - **Authentication**: nuxt-auth-utils 0.5.25 with OAuth2/Keycloak - **Type Safety**: TypeScript 5.7.3 - **API Client**: Auto-generated from OpenAPI spec using openapi-generator-cli #### Backend - **Framework**: Spring Boot 3.4.2 - **Language**: Kotlin 1.9.25 with Java 21 - **ORM**: Spring Data JPA with Hibernate - **Database**: PostgreSQL (with H2 for testing) - **Database Migrations**: Liquibase - **Authentication**: Spring Security with OAuth2 Resource Server (JWT) - **API Documentation**: SpringDoc OpenAPI (Swagger UI) - **PDF Generation**: OpenHTMLToPDF 1.0.10 with Thymeleaf templates - **Testing**: Spring Boot Testcontainers - **Code Quality**: ktlint 1.5.0 - **Build Tool**: Gradle #### Infrastructure - **Containerization**: Docker - **Orchestration**: Docker Compose (dev and prod configurations) - **CI/CD**: Local act execution with GitHub Actions format (`.github/workflows/pipeline.yaml`) --- ## Core Domain Concepts ### 1. Organizations & Multi-tenancy Users are assigned to **organizations**, which serve as the primary tenant boundary. Each organization: - Has its own isolated set of application forms - Has independent notification streams - Has organization-specific roles and permissions - Is identified by `organizationId` (string) ### 2. Application Forms The central entity of the system. Application forms can be: - **Templates** (`isTemplate: true`): Reusable form blueprints - **Instances** (`isTemplate: false`): Actual forms being worked on **Form Status Lifecycle**: ``` DRAFT → SUBMITTED → APPROVED/REJECTED → SIGNED ``` Each form has: - Name and description - Status tracking - Audit fields (createdBy, lastModifiedBy, createdAt, modifiedAt) - Organization association - Hierarchical structure of form elements ### 3. Dynamic Form Structure Forms follow a **three-level hierarchy**: ``` Application Form └── Section (FormElementSection) ├── title, shortTitle, description └── SubSection (FormElementSubSection) ├── title, subtitle └── Form Elements (FormElement) ├── id (UUID - generated by backend) ├── reference (string - custom key like "art_der_massnahme") ├── type (SELECT, CHECKBOX, RADIOBUTTON, TEXTFIELD, TEXTAREA, SWITCH, RICH_TEXT, DATE) ├── title, description ├── options (FormOption[]) │ ├── value, label │ ├── processingPurpose │ └── employeeDataCategory └── visibilityCondition (FormElementVisibilityCondition) ├── formElementConditionType (SHOW, HIDE) ├── sourceFormElementReference (string - reference key of source element) ├── formElementExpectedValue (string - value to compare against) └── formElementOperator (EQUALS, NOT_EQUALS, IS_EMPTY, IS_NOT_EMPTY) ``` **Dynamic Addition**: Users can add new form elements to any subsection at runtime via the API endpoint: ``` POST /application-forms/{applicationFormId}/subsections/{subsectionId}/form-elements?position={position} ``` ### 4. Version History & Snapshots Every update to an application form creates a new version: - Automatic versioning on form updates - Complete snapshots of form state (JSON-based) - Ability to restore previous versions - Audit trail with creator and timestamp - Versions are immutable once created ### 5. PDF & HTML Generation Forms can be exported as: - **HTML**: Rendered using Thymeleaf templates (`templates/application_form_template.html`) - **PDF**: Generated from HTML using OpenHTMLToPDF library Endpoints: - `GET /application-forms/{id}/html` - Returns HTML representation - `GET /application-forms/{id}/pdf` - Returns PDF (inline display) ### 6. Comments System Comments enable collaborative discussions: - Attached to specific **form elements** (not sections/subsections) - Associated with an application form - Full CRUD operations - Tracked with creator and timestamps - Paginated retrieval per form ### 7. Notifications Three types of notifications: 1. **User-specific**: Directed to a single user by `recipientId` 2. **Role-based**: Sent to all users with specific roles (`targetRoles[]`) 3. **Organization-wide**: Sent to all members (when both recipientId and targetRoles are null) Notification types: - `INFO`, `WARNING`, `ERROR` Features: - Mark individual/all as read - Clear all notifications - Get unread count (public endpoint for polling) - Pagination support ### 8. GDPR Compliance Features Each form option includes: **Processing Purpose**: - `SYSTEM_OPERATION` - `BUSINESS_PROCESS` - `DATA_ANALYSIS` - `NONE` **Employee Data Category**: - `NON_CRITICAL` - `REVIEW_REQUIRED` - `SENSITIVE` - `NONE` These enable compliance tracking and risk assessment. ### 9. Role-Based Access Control User roles in the system: - `CHIEF_EXECUTIVE_OFFICER` - `BUSINESS_DEPARTMENT` - `IT_DEPARTMENT` - `HUMAN_RESOURCES` - `HEAD_OF_WORKS_COUNCIL` - `WORKS_COUNCIL` - `EMPLOYEE` Roles are managed via Keycloak and enforced using Spring Security's `@PreAuthorize` annotations. ### 10. Conditional Form Element Visibility Form elements can be conditionally shown or hidden based on the values of other form elements. This enables dynamic forms that adapt to user input. **Key Concepts**: - **Visibility Conditions**: Rules attached to form elements that determine when they should be visible - **Source Element**: The form element whose value is being evaluated - **Target Element**: The form element with visibility conditions (the one being shown/hidden) - **Condition Types**: - `SHOW`: Element is visible only if the condition is met - `HIDE`: Element is hidden if the condition is met - **Operators**: - `EQUALS`: Source value exactly matches expected value (case-insensitive) - `NOT_EQUALS`: Source value does not match expected value - `IS_EMPTY`: Source value is empty string - `IS_NOT_EMPTY`: Source value is not empty **Implementation Details**: - Visibility conditions reference other form elements by their **reference key** (custom string identifier) - Reference keys are stable and meaningful (e.g., "art_der_massnahme", "testphase_findet_statt") - Each form element can have one visibility condition (single object, not array) - Hidden fields are automatically excluded from validation - PDF/HTML exports only include currently visible fields based on form state - Versioning system captures visibility conditions in snapshots - Conditions check the **value** property of form options, not labels **Example Configuration**: ```json { "reference": "testphase_zeitraum", "title": "Testphase Zeitraum", "description": "Zeitraum der Testphase", "type": "TEXTAREA", "options": [ { "value": "", "label": "Testphase Zeitraum", "processingPurpose": "SYSTEM_OPERATION", "employeeDataCategory": "NON_CRITICAL" } ], "visibilityCondition": { "formElementConditionType": "SHOW", "sourceFormElementReference": "testphase_findet_statt", "formElementExpectedValue": "Ja", "formElementOperator": "EQUALS" } } ``` In this example, the "Testphase Zeitraum" field is only visible when the form element with reference `testphase_findet_statt` has the value "Ja" selected. **Frontend Behavior**: - Visibility is evaluated client-side using the `useFormElementVisibility` composable - Form elements smoothly appear/disappear as conditions change - Hidden elements are excluded from the form submission **Backend Behavior**: - Visibility is evaluated server-side when generating PDF/HTML exports - Backend filters form elements before rendering to Thymeleaf templates - Hidden elements are not included in exported documents **Best Practices**: - Avoid circular dependencies (A depends on B, B depends on A) - Use meaningful reference keys (snake_case recommended, e.g., "art_der_massnahme", "testphase_findet_statt") - Reference keys should be unique within a form - Keep visibility logic simple for better user experience - For radio buttons and selects, use the actual label text as the value - For checkboxes, checked = "true", unchecked = "false" - Test visibility conditions thoroughly before deployment ### 11. Dynamic Section Spawning Form elements can trigger the dynamic creation of full sections based on user input. This enables complex workflows where additional form sections appear when certain conditions are met. **Key Concepts**: - **Section Templates**: Pre-defined section blueprints marked with `isTemplate: true` - **Spawn Triggers**: Rules on form elements that define when to spawn a section template - **Clonable Elements**: Form elements that can be duplicated by users (e.g., adding multiple modules) - **Title Interpolation**: Section titles can include placeholders like `{{triggerValue}}` **Section Template Properties**: - `isTemplate: boolean` - If true, section is a template (excluded from display/PDF) - `templateReference: string` - Unique identifier for the template - `titleTemplate: string` - Title with placeholder (e.g., "Modul: {{triggerValue}}") - `spawnedFromElementReference: string` - Links spawned instance to trigger element **Form Element Properties for Spawning**: - `sectionSpawnTrigger`: Defines spawn conditions - `templateReference`: Which template to spawn - `sectionSpawnConditionType`: SHOW or HIDE - `sectionSpawnExpectedValue`: Value to trigger spawning - `sectionSpawnOperator`: EQUALS, NOT_EQUALS, IS_EMPTY, IS_NOT_EMPTY - `isClonable: boolean` - Shows "Add another" button when true **Example - Clonable Module with Dynamic Section**: ```json { "reference": "modul_1", "title": "Modulname", "type": "TEXTAREA", "isClonable": true, "sectionSpawnTrigger": { "templateReference": "module_details_template", "sectionSpawnConditionType": "SHOW", "sectionSpawnOperator": "IS_NOT_EMPTY" } } ``` **Example - Template Section**: ```json { "title": "Moduldetails", "isTemplate": true, "templateReference": "module_details_template", "titleTemplate": "Modul: {{triggerValue}}", "formElementSubSections": [ { "title": "Modulinformationen", "formElements": [...] } ] } ``` **Client-Side Spawning Flow**: 1. User fills trigger element (e.g., types "SAP Finance") 2. Frontend clones template into CreateFormElementSectionDto (no ID) 3. Title interpolated: "Modul: SAP Finance" 4. User clicks "Add another" → new element clone created 5. User saves → backend generates IDs **Element Cloning Implementation**: - **Deep Cloning**: Elements are deep-cloned using `JSON.parse(JSON.stringify())` to avoid shared references between cloned elements - **Reference Generation**: Cloned elements get auto-generated references (e.g., `modul_1` → `modul_2` → `modul_3`) - **Value Reset**: TEXTAREA and TEXTFIELD elements have their values reset to empty string when cloned - **ID Removal**: Cloned elements don't have IDs (they're `CreateFormElementDto`), IDs are generated by the backend on save - **Independent Instances**: Each cloned element is completely independent - updating one doesn't affect others **Spawn Trigger Evaluation**: - Spawn triggers are evaluated when form element values change - Each element with a unique reference can spawn its own section independently - Multiple spawns are handled sequentially using `resultSections` to ensure correct state - Spawned sections are linked to their trigger element via `spawnedFromElementReference` **Element Update Matching**: - Form element updates use unique identifiers (`id` or `reference`) instead of array indices - This prevents cross-element updates when elements are filtered by visibility - Matching priority: `id` (if present) → `reference` (fallback) **Backend Behavior**: - Template sections (`isTemplate=true`) are filtered out from PDF/HTML exports - Spawned sections are included in exports with their interpolated titles - Versioning captures all template and spawn trigger fields **Frontend Composables**: - `useSectionSpawning`: Handles spawn trigger evaluation and template cloning - `useClonableElements`: Handles element cloning with reference generation and deep cloning --- ## Project Structure ### Frontend Directory Layout ``` legalconsenthub/ ├── app/ │ ├── app.config.ts # App-level configuration │ ├── app.vue # Root component │ ├── assets/css/ # Global styles │ ├── components/ # Vue components │ │ ├── formelements/ # Form input components │ │ ├── DeleteModal.vue │ │ ├── FormEngine.vue # Core form rendering engine │ │ ├── FormStepperWithNavigation.vue │ │ ├── FormValidationIndicator.vue │ │ ├── NotificationsSlideover.vue │ │ ├── RestoreVersionModal.vue │ │ ├── ServerConnectionOverlay.vue │ │ ├── TheComment.vue │ │ ├── UserMenu.vue │ │ ├── VersionComparisonModal.vue │ │ └── VersionHistory.vue │ ├── composables/ # Business logic composables │ │ ├── applicationForm/ │ │ ├── applicationFormTemplate/ │ │ ├── applicationFormVersion/ │ │ ├── comment/ │ │ ├── notification/ │ │ ├── complianceMap.ts │ │ ├── useApplicationFormNavigation.ts │ │ ├── useApplicationFormValidator.ts │ │ ├── useClonableElements.ts │ │ ├── useFormElementManagement.ts │ │ ├── useFormElementVisibility.ts │ │ ├── useFormStepper.ts │ │ ├── usePermissions.ts │ │ ├── useSectionSpawning.ts │ │ └── useServerHealth.ts │ ├── layouts/ # Layout components │ │ ├── auth.vue │ │ └── default.vue │ ├── middleware/ # Route middleware │ │ ├── auth.global.ts # Authentication check │ │ ├── permissions.global.ts # Authorization check │ │ └── refreshToken.global.ts # Token refresh logic │ ├── pages/ # Route pages │ │ ├── settings.vue # User settings page (language, theme, appearance) │ │ ├── administration.vue # Admin template editor │ │ ├── application-forms/ │ │ ├── callback.vue # OAuth callback │ │ ├── create.vue │ │ ├── index.vue │ │ └── login.vue │ ├── plugins/ # Nuxt plugins │ │ ├── error-handler.ts │ │ └── server-health.client.ts │ ├── stores/ # Pinia stores │ │ ├── useCommentStore.ts │ │ └── useUserStore.ts │ └── utils/ # Utility functions │ ├── date.ts │ ├── formDiff.ts │ └── wrappedFetch.ts ├── server/ # Nuxt server endpoints │ ├── api/[...].ts # Proxy to backend API │ └── routes/auth/ # OAuth routes ├── i18n/locales/ # Translation files │ ├── de.json │ └── en.json ├── nuxt.config.ts # Nuxt configuration ├── package.json └── tsconfig.json ``` ### Backend Directory Layout ``` legalconsenthub-backend/ ├── src/main/ │ ├── kotlin/com/betriebsratkanzlei/legalconsenthub/ │ │ ├── application_form/ │ │ │ ├── ApplicationForm.kt # Entity │ │ │ ├── ApplicationFormController.kt # REST controller │ │ │ ├── ApplicationFormFormatService.kt # PDF/HTML generation │ │ │ ├── ApplicationFormMapper.kt # DTO ↔ Entity mapper │ │ │ ├── ApplicationFormRepository.kt # JPA repository │ │ │ ├── ApplicationFormService.kt # Business logic │ │ │ └── PagedApplicationFormMapper.kt │ │ ├── application_form_template/ # Template-specific logic │ │ ├── application_form_version/ # Version management │ │ ├── comment/ # Comment domain │ │ ├── config/ # Security, TestContainers │ │ ├── error/ # Custom exceptions & handler │ │ ├── form_element/ # Form structure entities │ │ │ ├── FormElement.kt │ │ │ ├── FormElementSection.kt │ │ │ ├── FormElementSubSection.kt │ │ │ ├── FormOption.kt │ │ │ └── [Mappers for each] │ │ ├── notification/ # Notification domain │ │ ├── security/ # JWT authentication │ │ │ ├── CustomJwtAuthentication.kt │ │ │ ├── CustomJwtAuthenticationConverter.kt │ │ │ ├── CustomJwtTokenPrincipal.kt │ │ │ └── JwtUserSyncFilter.kt │ │ ├── user/ # User domain │ │ └── LegalconsenthubApplication.kt # Spring Boot main │ ├── resources/ │ │ ├── application.yaml # Main config │ │ ├── application-h2.yaml # H2 profile │ │ ├── application-testcontainers.yaml # Testcontainers profile │ │ ├── db/changelog/ # Liquibase migrations │ │ └── templates/ │ │ └── application_form_template.html # Thymeleaf template for PDF/HTML │ └── test/ ├── build.gradle # Gradle build config ├── Dockerfile └── settings.gradle ``` ### API Specification ``` api/ ├── legalconsenthub.yml # Main OpenAPI 3.0.3 spec └── legalconsenthub-middleware.yml # Middleware API spec (if exists) ``` --- ## Development Patterns & Conventions ### 1. API-First Development with OpenAPI The project uses **OpenAPI 3.0.3** as the single source of truth: **Frontend**: ```bash pnpm run api:generate # Generates TypeScript fetch clients to .api-client/ ``` **Backend**: ```gradle ./gradlew generate_legalconsenthub_server # Generates Kotlin Spring interfaces to build/generated/server/ ``` Controllers implement the generated API interfaces. ### 2. Mapper Pattern (Backend) All DTO ↔ Entity conversions happen in separate **Mapper classes**: - `ApplicationFormMapper` - `FormElementMapper` - `CommentMapper` - etc. **Rule**: Never perform mapping logic inside services or controllers. ### 3. Composables Pattern (Frontend) Business logic is encapsulated in Vue composables: - Grouped by domain (e.g., `composables/applicationForm/`) - Exported through `composables/index.ts` - Reusable across components Examples: - `useApplicationFormNavigation()` - Form navigation logic - `useApplicationFormValidator()` - Validation rules - `useFormElementManagement()` - Dynamic form element operations ### 4. Manual Database Migrations **Important**: Do NOT auto-generate SQL migrations. Migrations are managed manually using **Liquibase** in `src/main/resources/db/changelog/`. ### 5. Code Quality & Linting **Frontend**: - ESLint with @nuxt/eslint - Prettier for formatting - TypeScript strict mode **Backend**: - ktlint for Kotlin formatting - Gradle task: `./gradlew ktlintCheck` ### 6. Authentication Flow 1. User clicks login → Redirects to Keycloak 2. Keycloak authenticates → Returns to `/callback` 3. Backend exchanges code for JWT 4. JWT stored in secure cookie (`nuxt-auth-utils`) 5. Frontend middleware (`auth.global.ts`) checks auth on every route 6. Backend validates JWT on every API request ### 7. Error Handling **Backend**: Custom exception classes with `@RestControllerAdvice` handler **Frontend**: Global error handler plugin + local try/catch blocks --- ## API Endpoints Overview ### Application Forms - `GET /application-forms` - List all forms (filtered by organizationId) - `POST /application-forms` - Create new form - `GET /application-forms/{id}` - Get form by ID - `PUT /application-forms/{id}` - Update form - `DELETE /application-forms/{id}` - Delete form - `GET /application-forms/{id}/pdf` - Export as PDF - `GET /application-forms/{id}/html` - Export as HTML - `POST /application-forms/{id}/submit` - Submit form - `POST /application-forms/{applicationFormId}/subsections/{subsectionId}/form-elements?position={n}` - Add form element dynamically ### Application Form Templates - `GET /application-form-templates` - List templates - `POST /application-form-templates` - Create template - `GET /application-form-templates/{id}` - Get template - `PUT /application-form-templates/{id}` - Update template - `DELETE /application-form-templates/{id}` - Delete template ### Version History - `GET /application-forms/{id}/versions` - List all versions - `GET /application-forms/{id}/versions/{versionNumber}` - Get specific version - `POST /application-forms/{id}/versions/{versionNumber}/restore` - Restore version ### Comments - `GET /application-forms/{applicationFormId}/comments` - Get comments for form - `POST /application-forms/{applicationFormId}/form-elements/{formElementId}/comments` - Create comment - `PUT /comments/{id}` - Update comment - `DELETE /comments/{id}` - Delete comment ### Notifications - `GET /notifications?organizationId={id}` - Get notifications (paginated) - `GET /notifications/unread?organizationId={id}` - Get unread notifications - `GET /notifications/unread/count?userId={id}&organizationId={id}` - Get unread count (public) - `POST /notifications` - Create notification - `PUT /notifications/{id}/mark-read?organizationId={id}` - Mark as read - `PUT /notifications/mark-all-read?organizationId={id}` - Mark all as read - `DELETE /notifications/clear-all?organizationId={id}` - Clear all notifications ### Users - `GET /users/{id}` - Get user by ID - `DELETE /users/{id}` - Delete user ### Roles - `GET /roles` - List all roles - `POST /roles` - Create role - `GET /roles/{id}` - Get role - `DELETE /roles/{id}` - Delete role --- ## Configuration ### Frontend Environment Variables Set in `nuxt.config.ts` → `runtimeConfig.public`: - `clientProxyBasePath` - Frontend proxy path to backend - `serverApiBaseUrl` - Direct backend URL (server-side) - `serverApiBasePath` - Backend API base path - `keycloakTokenUrl` - Keycloak token endpoint OAuth configuration (`runtimeConfig.oauth.keycloak`): - `clientId`, `clientSecret`, `realm`, `serverUrl`, `redirectURL` - Scopes: `openid`, `organization` ### Backend Configuration Main config in `src/main/resources/application.yaml`: - Spring profiles: `h2`, `testcontainers` - Database connection (PostgreSQL) - Keycloak JWT validation - Server port (default: 8080) --- ## Running the Application ### Development Setup **Prerequisites**: - Node.js 22.16.0 (managed via Volta) - pnpm 10.11.0+ - Java 21 - PostgreSQL (or use Docker) - act (for running CI/CD workflows locally) **Frontend**: ```bash cd legalconsenthub pnpm install pnpm run dev # Runs on port 3001 ``` **Backend**: ```bash cd legalconsenthub-backend ./gradlew bootRun # Runs on port 8080 ``` ### Docker Deployment **Development**: ```bash docker-compose -f deployment/docker-compose-dev.yaml up ``` **Production**: ```bash docker-compose -f deployment/docker-compose-prod.yaml up ``` ### CI/CD with Act Workflows are executed locally using nektos/act: ```bash # List workflows act -l # Run specific jobs act -j frontend act -j backend # Run all jobs act push # Run with secrets (for Docker push/deploy) act push --secret-file .secrets # Dry run act -n ``` --- ## Testing ### Frontend - Type checking: `pnpm run type-check` - Linting: `pnpm run lint` ### Backend - Run tests: `./gradlew test` - Uses Testcontainers for integration tests --- ## Important Notes for AI Assistants 1. **Never auto-generate SQL migrations** - Always prompt the developer to create them manually 2. **Use mapper classes** for all DTO/Entity conversions in backend 3. **Prefer existing types** - Don't create duplicate type definitions 4. **No redundant comments** - Don't add comments about what changed or redundant explanations 5. **Organization context** - Always consider organizationId for data isolation 6. **Form structure is three-level** - Section → SubSection → Element (not two-level) 7. **API generation is automatic** - After modifying OpenAPI spec, run generation commands 8. **Roles are managed in Keycloak** - Not in the application database 9. **PDF/HTML export must be re-validated when form elements change** - Any change that touches form element types, options/value storage, visibility conditions, or section spawning can break PDF output. - Always validate **both** HTML and PDF export for a real form instance (including newly spawned sections and newly added text fields with values): - `GET /application-forms/{id}/html` - `GET /application-forms/{id}/pdf` - Verify these render correctly (with saved values): - `TEXTFIELD`, `TEXTAREA`, `DATE`, `RICH_TEXT` - `SELECT`, `RADIOBUTTON`, `CHECKBOX`, `SWITCH` - If you changed backend filtering or templating, ensure: - Template sections (`isTemplate=true`) remain excluded from export - Spawned sections (`isTemplate=false`, `spawnedFromElementReference` set) are included - Visibility filtering does not drop elements unintentionally (e.g. missing `reference` must not hide elements by default) - Hotspots: - `legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormFormatService.kt` - `legalconsenthub-backend/src/main/resources/templates/application_form_template.html` 10. **Keep `CLAUDE.md` current** - If you introduce a change that is worth being documented for future AI prompts (architecture, domain rules, workflows, endpoints, config, auth, generation steps, export logic, multi-tenancy constraints, etc.), update `CLAUDE.md` in the same change set. 11. **Clean Code: order functions by abstraction (top-down, like a book)** - Put the highest-level, most readable entry points first (public API / primary flows), then progressively lower-level helpers below. - Keep functions at the same level of abstraction grouped together; details should live “later” (below) so the file reads top-to-bottom without jumping around. 12. **No hardcoded UI strings** (i18n) - Never hardcode user-facing text in Vue components/composables. - Always add UI strings to `legalconsenthub/i18n/locales/de.json` and `legalconsenthub/i18n/locales/en.json` and reference them via `$t()` / `useI18n()`. --- ## Additional Resources - OpenAPI Specification: `api/legalconsenthub.yml` - Thymeleaf Template: `legalconsenthub-backend/src/main/resources/templates/application_form_template.html` - CI/CD Pipeline: `.github/workflows/pipeline.yaml` - Act Configuration: `.actrc` and `.secrets.example` - Database Management Script: `manage-db.sh` - Test Data: `testdata.json` - IT System Config: `it-system.json`