Commit Graph

87 Commits

Author SHA1 Message Date
Marcel
706c029a5e fix(ci): pin DOCKER_API_VERSION=1.43 for e2e job
Some checks failed
CI / Unit & Component Tests (push) Successful in 9m49s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / E2E Tests (push) Failing after 13m6s
CI / Unit & Component Tests (pull_request) Successful in 11m14s
CI / Backend Unit Tests (pull_request) Successful in 2m18s
CI / E2E Tests (pull_request) Failing after 13m20s
The runner's Docker client negotiates API 1.53 but the daemon on the
NAS only supports up to 1.43. Pin the version for all docker commands
in the e2e job, including the new network connect step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:32:27 +01:00
Marcel
c023d5b0a2 fix(ci): connect job container to compose network for DB/MinIO access
Some checks failed
CI / Unit & Component Tests (push) Successful in 9m36s
CI / Backend Unit Tests (push) Successful in 1m54s
CI / E2E Tests (push) Failing after 1m39s
CI / Unit & Component Tests (pull_request) Successful in 9m16s
CI / Backend Unit Tests (pull_request) Successful in 1m59s
CI / E2E Tests (pull_request) Failing after 2m8s
The act_runner job runs inside a Docker container. Docker Compose port
mappings bind to the Docker host, not the job container's localhost —
so localhost:5433 was always refused.

Fix: after compose starts, connect the job container (identified by
/etc/hostname) to the archive-net compose network. Then switch the
backend startup args to use service names db:5432 and minio:9000
instead of host-mapped ports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:00:51 +01:00
Marcel
a87f40d0ea fix(ci): pass docker-compose.ci.yml override to e2e compose commands
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 9m19s
CI / Backend Unit Tests (pull_request) Successful in 1m54s
CI / E2E Tests (pull_request) Failing after 4m14s
The e2e job was calling plain `docker compose up` without the CI
override file, so it used the base compose bind-mount for MinIO
(./data/minio) which doesn't exist on the runner. The CI override
replaces bind mounts with ephemeral named volumes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:06:38 +01:00
Marcel
5044f99aac fix(api): use API_INTERNAL_URL in tags and persons proxy routes
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Both SvelteKit API proxy routes were hardcoding http://localhost:8080,
breaking typeahead search in Docker environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:58:21 +01:00
Marcel
a52b8a0694 fix(auth): use API_INTERNAL_URL in userGroup hook
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
The userGroup hook was hardcoding http://localhost:8080 instead of
reading API_INTERNAL_URL from the environment. In Docker this caused
the /api/users/me fetch to fail silently, leaving event.locals.user
unset and triggering the handleAuth guard to redirect every page to
/login — including the login form action itself, creating an infinite
redirect loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:56:59 +01:00
Marcel
0918e75803 docs: add Red/Green TDD workflow to COLLABORATING.md
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 8m48s
CI / Backend Unit Tests (pull_request) Successful in 1m56s
CI / E2E Tests (pull_request) Failing after 58s
CI / Unit & Component Tests (push) Successful in 8m22s
CI / Backend Unit Tests (push) Successful in 1m59s
CI / E2E Tests (push) Failing after 52s
2026-03-19 12:03:50 +01:00
Marcel
56cbd290e3 fix(e2e): fix Playwright E2E test suite for CI
- Replace __dirname with fileURLToPath(import.meta.url) for ESM compatibility
- Start SvelteKit dev server on port 3000 with 120s webServer timeout
- Add data-hydrated attribute (set in onMount) so tests wait for hydration
- Fix nav active class assertions: text-brand-navy (not border-brand-navy)
- Fix filter button selector: exact match to avoid matching "Alle Filter löschen"
- Fix date validation test: use pressSequentially('99') to trigger dateInvalid
- Fix person/document search: navigate directly to URL with query param
  (avoids debounced oninput → goto race condition in CI)
- Fix heading selector: level: 1 to avoid strict-mode with h1+h2 on page
- Fix auth redirect: return 401 from handleFetch instead of throwing redirect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:37 +01:00
Marcel
9f3f022ec0 ci: set up CI pipeline with unit, backend, and E2E test jobs
- Add unit-tests job using Playwright Docker image (no apt install needed)
- Add backend-unit-tests job with Java 21 + Maven
- Add e2e-tests job: PostgreSQL + MinIO via docker-compose, Spring Boot backend,
  SvelteKit dev server, Playwright Chromium
- Use non-conflicting host ports (DB: 15432, MinIO: 19000/19001)
- Install Docker CLI via official Docker apt repo (Playwright image has no daemon)
- Connect job container to archive-net for direct DB/MinIO access
- Pin DOCKER_API_VERSION=1.43 for Docker socket compatibility
- Start backend with java -jar + health-check loop (curl /actuator/health)
- Use continue-on-error on cleanup step to handle SIGKILL gracefully
- Downgrade upload-artifact to v3 (v4 not supported on self-hosted Gitea)
- Always run npm ci unconditionally (actions/cache@v4 broken on this runner)
- Log /tmp/backend.log on startup timeout so Spring Boot errors are visible
- Add diagnostic steps for DB tables and Flyway schema history

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:37 +01:00
Marcel
6ef7b292cc fix(flyway): baseline existing schemas at V4 on first run
Local dev databases that existed before Flyway was introduced have tables
but no flyway_schema_history. Flyway refuses to migrate a non-empty schema
without a history table. baselineOnMigrate=true with baselineVersion=4
stamps those databases as already at V4 without re-running migrations.
Fresh databases (CI) have an empty schema so the baseline is never
triggered and all 4 migrations run normally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
a65cbf9bae fix(backend): switch base image from alpine to debian for bash compatibility
mvnw is a bash script; eclipse-temurin:21-jdk-alpine only provides ash
(busybox), causing the container to exit silently with code 0 before the
JVM starts. The Debian-based eclipse-temurin:21-jdk image includes bash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
a60905674f fix(backend): explicit Flyway bean to bypass broken auto-configuration
Spring Boot 4.0 Flyway auto-configuration is not triggering in the CI
environment — confirmed by empty DB and no flyway_schema_history table.
Replace YAML-based auto-config with an explicit @Bean that creates and
runs Flyway directly on startup, independent of any auto-configuration
conditions. Disable the auto-config via spring.flyway.enabled=false to
prevent interference. Add @DependsOn("flyway") to DataInitializer to
enforce that CommandLineRunner beans are only registered after migrations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
e6db43850b fix(security): permit /actuator/health without authentication
The CI health check (curl -sf) and Docker Compose health check (wget)
both hit /actuator/health unauthenticated. With anyRequest().authenticated()
the endpoint returned 401, curl -f treated it as failure, and the health
check loop never exited successfully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
802f1ab0e0 fix(backend): explicit Flyway config and DataInitializer null title fix
Adding explicit spring.flyway.* config (url/user/password) ensures Flyway
creates its own JDBC connection and runs migrations independently of the JPA
datasource initialization order in Spring Boot 4.0.

Fix DataInitializer creating a Document with title=null, which would hit the
NOT NULL constraint in the documents table once the admin user init succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
9b67db74eb feat: auto-start Spring Boot backend via docker-compose
Replace the devcontainer (sleep infinity + VS Code image) with a proper
dev setup:
- Dockerfile: eclipse-temurin:21-jdk-alpine running ./mvnw spring-boot:run
- Source mounted at /app, Maven deps cached in named volume maven_cache
- Healthcheck on /actuator/health so frontend waits until backend is ready
- frontend depends_on backend: service_healthy (was service_started)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
3280125140 feat: add frontend dev container to docker-compose
- frontend/Dockerfile: Node 20 Alpine image running npm run dev
- docker-compose: frontend service with depends_on db/minio/backend,
  source mounted as volume, named volume for node_modules to avoid
  OS binary conflicts between host and container
- vite.config.ts: make API proxy target configurable via
  API_PROXY_TARGET env var (defaults to localhost:8080 for local dev,
  set to http://backend:8080 inside Docker)
- .env: update PORT_FRONTEND to 5173 (actual vite dev server port)

Usage:
  docker compose up frontend   # starts frontend + all dependencies
  docker compose up            # starts everything

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
553fa8a4b9 ci: cache Maven repository explicitly for both Java jobs
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
The built-in cache: maven in setup-java@v4 does not reliably work
on self-hosted act runners. Replace with an explicit actions/cache@v4
on ~/.m2/repository keyed on pom.xml hash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 22:06:35 +01:00
Marcel
409e70078c ci: cache node_modules and Playwright browser binary
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
node_modules and the Playwright Chromium binary were downloaded fresh
on every run, making setup account for ~99% of pipeline runtime.

- Cache frontend/node_modules keyed on package-lock.json hash
- Cache ~/.cache/ms-playwright keyed on package-lock.json hash
- On cache hit: skip npm ci and browser download, only reinstall
  system deps (install-deps) which is much faster than a full install

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 22:03:40 +01:00
Marcel
5f36930b6b ci: use non-standard ports for DB and MinIO in e2e job
Some checks failed
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (push) Successful in 17m15s
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
Avoids conflicts with any system services (PostgreSQL, MinIO) that
may already occupy 5432/9000/9001 on the runner host.
DB: 5433, MinIO API: 9100, MinIO console: 9101.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 21:44:09 +01:00
Marcel
b456c8f1bd ci: free host ports before starting e2e infrastructure
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Port 5432 was already in use after docker compose cleanup because a
system-level PostgreSQL service on the runner host holds the port.
Also kill any stray containers binding to 5432/9000/9001.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 21:41:58 +01:00
Marcel
3f987ca48f ci: add backend unit test job to pipeline
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
Runs ./mvnw clean test in a dedicated job — no DB or S3 needed
since all tests use Mockito or WebMvcTest slices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 21:37:50 +01:00
Marcel
225d6e44c9 test(backend): add service unit tests and controller slice tests
Some checks failed
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
Service unit tests (Mockito, no Spring context):
- DocumentServiceTest — getById, updateDocument, deleteTagCascading, createPlaceholder
- PersonServiceTest — getById, findOrCreateByAlias, mergePersons
- TagServiceTest — getById, findOrCreate, update, delete
- UserServiceTest — findByUsername, deleteUser, createUserOrUpdate, getGroupById

Controller slice tests (@WebMvcTest):
- DocumentControllerTest — 401/403/200 for GET /search, POST /, PUT /{id}
- TagControllerTest — 401/403/200 for GET, PUT /{id}, DELETE /{id}

Also removes FamilienarchivApplicationTests (full @SpringBootTest
requires DB + S3 infrastructure; covered by e2e job instead).

65 tests total, all passing.
Refs #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 21:33:24 +01:00
Marcel
ded5c24c40 doc: add branching and PR rules to collaboration guide
Some checks failed
CI / Unit & Component Tests (push) Successful in 17m20s
CI / E2E Tests (push) Failing after 36s
All changes must go through a feature branch and PR for review
before merging to main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:59:38 +01:00
Marcel
f4e6fe587c fix(ci): tear down leftover containers before e2e run
Some checks failed
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
Port 5432 was already bound by a zombie container from a previous
failed run, preventing docker compose from starting the DB.
Add a cleanup step at the top of the e2e job to ensure a clean state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:57:33 +01:00
Marcel
0d0aa83c0c fix(test): fix login page title test after DGB logo removal
Some checks failed
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
The logo was changed from an SVG to a plain <span>, causing
getByText('Familienarchiv') to match both the logo and the footer.
- Update test to use getByRole('link') for precision
- Remove "De Gruyter" from footer text to align with branding change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:55:34 +01:00
Marcel
dcb26e201a fix(ci): replace docker-compose v1 with docker compose v2
Some checks failed
CI / Unit & Component Tests (push) Failing after 12m35s
CI / E2E Tests (push) Failing after 1m1s
The act runner does not have the standalone docker-compose binary.
Replace both occurrences with the v2 plugin syntax (space instead of hyphen).
Closes #3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:25:06 +01:00
Marcel
fcaf6dc322 fix: show pointer 2026-03-17 18:52:52 +00:00
Marcel
f59f4d304a fix: reflect url filters on page 2026-03-17 18:47:10 +00:00
Marcel
f2030ca4ee refactor: remove de gruyter icon 2026-03-17 18:46:05 +00:00
Marcel
49fd29c1e6 refactor: use de gruyter ci 2026-03-17 18:35:13 +00:00
Marcel
3b04f4cafe doc: add styleguide 2026-03-17 18:06:50 +00:00
Marcel
ed32e1728d doc: add collab rules
Some checks failed
CI / Unit & Component Tests (push) Successful in 16m0s
CI / E2E Tests (push) Failing after 1m23s
2026-03-17 16:07:50 +00:00
Marcel
273b43261f chore: update ignore file 2026-03-17 13:35:32 +00:00
Marcel
7cb20dec50 test: add e2e tests 2026-03-17 13:34:05 +00:00
Marcel
973620a097 build: add mvn properties 2026-03-17 13:33:02 +00:00
Marcel
b3de5f885d ci: add workflows 2026-03-17 13:32:12 +00:00
Marcel
4417fc9828 refactor: migrate all Svelte components from Svelte 4 to Svelte 5 runes
- Replace `export let` with `$props()` and `$bindable()` across all components
- Replace `$:` reactive statements with `$derived()` and `$effect()`
- Replace `createEventDispatcher` with callback props (e.g. `onchange`)
- Replace `on:event` directives with inline event handlers (`onclick`, `oninput`, etc.)
- Replace `<slot />` with `{@render children()}` in layout
- Use `untrack()` for SSR-safe $state initialization from reactive props
- Replace `blur` + `setTimeout` anti-pattern in TagInput with `clickOutside` action
- Fix `page` store usage in layout to use `$app/state` directly
- 0 errors, 0 warnings after svelte-check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:43:26 +01:00
Marcel
25e095ea47 refactor: enforce Controller → Service → Repository layering throughout backend
- Created TagService: encapsulates all tag find/create/update/delete operations
- Extended PersonService: added findAll(), getById(), getAllById(), findOrCreateByAlias()
- Extended UserService: added createGroup(), updateGroup(), deleteGroup(), getGroupById()
- DocumentService: replaced direct PersonRepository/TagRepository access with
  PersonService/TagService calls; added getDocumentById(), getDocumentsBySender(),
  getConversationFiltered(), deleteTagCascading()
- MassImportService: replaced PersonRepository/TagRepository with PersonService/TagService
- PersonController: removed direct repo injections, delegates to PersonService/DocumentService
- DocumentController: removed DocumentRepository injection, delegates to DocumentService
- TagController: removed TagRepository/DocumentRepository, delegates to TagService/DocumentService
- GroupController: removed UserGroupRepository injection, delegates to UserService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 08:49:33 +01:00
Marcel
97e5255d7f refactor: move createPerson logic into PersonService
Controller was directly calling personRepository.save() for person creation.
Extracted into PersonService.createPerson() to enforce Controller → Service → Repository layering.
Also documented the layering rules in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 08:28:30 +01:00
Marcel
6b5c78f789 fix: align save bar width with form card on new person page
Replaced sticky full-bleed bar with a regular card-style row,
matching the form card width and adding mt-4 top margin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:25:44 +01:00
Marcel
0123dffdc4 feat: add create person feature via web interface
- Backend: new POST /api/persons endpoint in PersonController
- Frontend: new /persons/new route with Vorname/Nachname/Alias form,
  redirects to the new person's detail page on success
- Persons list: subtle '+ Neue Person' link below the page title

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:23:05 +01:00
Marcel
b583c8489d fix: disable overscroll bounce to keep header stationary
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:13:24 +01:00
Marcel
29c5f1ff81 fix: improve visibility of person search input
Added white background, explicit border, and rounded corners to make
the search field clearly visible against the sand-colored page background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:08:45 +01:00
Marcel
6fcff3355d feat: add create document feature via web interface
- Backend: new POST /api/documents endpoint with DocumentService.createDocument()
  reusing DocumentUpdateDTO; handles file upload, tags, sender, receivers
- Frontend: new /documents/new route with same four-section form as edit page
  (Wer & Wann, Beschreibung, Transkription, Datei) but with empty fields
- Home page: subtle '+ Neues Dokument' link above the document list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:04:56 +01:00
Marcel
c4806474df fix: format person document list dates with Intl API (20. Dezember 2026)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 15:48:06 +01:00
Marcel
d5d882fe88 fix: format home page document dates with Intl API (20. Dezember 2026)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:25:19 +01:00
Marcel
775e185e0f fix: format conversation date with Intl API (20. Dezember 2026)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:15:23 +01:00
Marcel
8a9e7bd9eb fix: resolve all 47 svelte-check errors and 10 a11y warnings
Root cause 1 — OpenAPI types: add @Schema(requiredMode=REQUIRED) to
non-nullable fields on Person, Tag, Document, AppUser, UserGroup;
regenerate api.ts so required fields are no longer optional.

Root cause 2 — Stale types: api.ts regenerated, picking up the Tag
endpoint fix from commit 62189d8 (List<Tag> instead of List<String>).

Root cause 3 — openapi-fetch error pattern: replace `if (apiError)`
(broken when error type is never/undefined) with `if (!result.response.ok)`
across all +page.server.ts files. Cast error via `unknown` to satisfy TS.

Root cause 4 — FormData casts: add `as string` / `as string[]` to
FormData.get() / FormData.getAll() calls in admin/+page.server.ts.

Standalone fixes:
- +page.server.ts: return error field so home page template compiles
- documents/[id]/+page.svelte: type loadFile param, remove invalid iframe `type`
- conversations: type documents as Document[] instead of unknown[]
- persons/[id]: non-null assert person data after ok-check

a11y: aria-label on all icon-only buttons in TagInput and admin page,
replace invalid <label> with <p> for compound controls, remove autofocus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:08:25 +01:00
Marcel
5921a10d2e fix: format document date with Intl API (20. Dezember 2026)
Use Intl.DateTimeFormat with de-DE locale to show full German month
name. Appending T12:00:00 to the ISO string avoids UTC-midnight
timezone shifts misrendering the day.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 11:06:41 +01:00
Marcel
1c708d41ad fix: display document date in German format (dd.mm.yyyy)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 11:04:26 +01:00
Marcel
1c6ea5ec30 feat: add inline validation hint for German date input
Show a red border and format hint ("TT.MM.JJJJ") only when the user
has typed something but the date is not yet complete. Focuses-only
or empty fields show no error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 11:02:08 +01:00