Commit Graph

46 Commits

Author SHA1 Message Date
Marcel
81ed1ce3ed test(admin): replace setTimeout timing hack with vi.waitFor in layout specs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 20:23:05 +02:00
Marcel
6d61297182 fix(tests): fix 27 failing frontend unit tests
Six categories of breakage:

1. date.ts — add formatGermanDateInput(raw: string): string as a pure
   function covering both digit-stream auto-dot and manual-dot-with-padding
   modes. Refactor handleGermanDateInput to delegate to it. Fixes 16 failures
   in date.spec.ts where the function was imported but didn't exist.

2. Admin layout specs (groups/tags/users) — $effect fires on initial mount
   with manualCollapse=false, so the spy captured 'false' before the click's
   effect ran. Fix: move spy setup after render(), add await setTimeout(0) to
   flush Svelte effects before asserting.

3. DashboardMentions — component now renders a persistent
   "Benachrichtigungsverlauf ansehen" link, making getByRole('link') strict-
   mode violations. Fix: scope link queries to the actor name, and check
   absence of the actor link (not all links) in the no-documentId test.

4. Conversations page — empty-state copy changed from "Wählen Sie zwei
   Personen aus" to "Korrespondenz durchsuchen". Update the test.

5. Login page — AuthHeader adds a second aria-label="Familienarchiv" link.
   Use .first() to avoid strict-mode violation.

6. Persons page — alias is rendered with German quotation marks „…" not
   straight quotes "…". Update the test string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:28:35 +02:00
Marcel
1541afd470 feat(focus-rings): update all form inputs and document components to ring-focus-ring
Replaces focus:border-ink, focus:ring-ink, focus:ring-primary, focus:ring-accent
patterns with focus-visible:ring-2 focus-visible:ring-focus-ring focus:outline-none
across: PersonEditForm, profile forms, admin forms, document sections,
conversation filter bars, persons/documents new forms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:22:11 +02:00
Marcel
7e43bd43a4 feat(dark-mode): replace neutral tokens with navy-tinted palette + fix WCAG AA
- Replace neutral dark tokens (#0d0d0d, #1a1a1a, etc.) with navy-tinted
  values derived from brand-navy: canvas #010e1e, surface #011526,
  overlay #011e38, muted #011a30
- Fix --c-ink-3 WCAG AA failure in [data-theme='dark'] block:
  #6b7280 (3.2:1, fail) → #8b97a5 (7.1:1, AAA ✓)
- Add color-scheme: dark to both dark blocks for native OS scrollbar theming
- Update PDF viewer tokens to navy palette (bg #010e1e, ctrl #011526, text #f0efe9)
- Add --c-header token (#ffffff light / #01335e dark) for independent
  header surface control; mapped to --color-header in @theme inline
- Fix EntityNav contrast: text-white/30 → /50 (heading) and text-white/20
  → /50 (inactive count badges) to pass WCAG AA 4.5:1 on bg-brand-navy

Closes #166

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 11:37:30 +02:00
Marcel
154f859efc feat(korrespondenz): address PR #164 review – blockers and suggestions
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) Failing after 1m36s
CI / Backend Unit Tests (pull_request) Failing after 2m36s
CI / E2E Tests (pull_request) Failing after 1h49m0s
Blockers (14):
- B1: fix senderName/receiverName to use $derived instead of $state + sync $effect
- B2: migrate all korrespondenz components from messages-extra shim to paraglide m.*
- B3: i18n CorrespondenzEmptyState (heading, subtext, search placeholder)
- B4: add response.ok checks to admin layout server load
- B5: add response.ok checks to korrespondenz page server load
- B6: add page.server.spec.ts with 5 test suites for korrespondenz load function
- B7: add axe-core accessibility checks to all e2e korrespondenz tests
- B8: add Testcontainers JPQL tests for findSinglePersonCorrespondence (DISTINCT + sender)
- B9: hide auth reset-token endpoint from OpenAPI spec; remove from generated api.ts
- B11: replace amber hardcoded hex colors in SinglePersonHintBar with brand tokens
- B12: replace clipboard emoji with Heroicons SVG in SinglePersonHintBar
- B13: create DateInput component (German dd.mm.yyyy); use it in CorrespondenzFilterControls
- B14: add Paraglide compile step to CI workflow before lint/test

Suggestions (11):
- S1: make CorrespondentSuggestionsDropdown a pure display component; lift fetch to PersonBar
- S2: fix leftover messages-extra import in ConversationTimeline; use brand tokens for status dots
- S3: add intent comment to EntityNav openFlyout behavior
- S4: rename canManageGroups → canManagePermissions throughout admin
- S6: remove domFlush helper from DateInput spec; use expect.poll instead
- S7: replace test.skip with throw new Error in bilateral e2e tests
- S8: add inverse aria-disabled test for filter strip
- S9: remove sm:min-h-0 from sort button to preserve 44px touch target
- S10: add title attributes to tablet trigger buttons in EntityNav
- S11: delete messages-extra.ts shim entirely

Also: fix admin pages revealing blank strip at bottom (-mb-6 on admin layout)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:57:48 +02:00
Marcel
393cb52178 fix(admin): address PR review feedback from all personas
Blockers resolved:
- localStorage key collision: UsersListPanel/GroupsListPanel/TagsListPanel
  now each use their own key (admin_*_list_collapsed)
- $effect autocollapse replaced with $derived(autocollapse || manualCollapse)
  across all three list panels (Felix — Svelte 5 rule violation)
- groups/new: add READ_ALL and ANNOTATE_ALL to available standard permissions
- Mobile back-to-list links added to all five detail panel headers (md:hidden)
  so users landing directly on a detail URL on mobile can navigate back
- onDestroy(() => stopPolling()) added to system/+page.svelte (Tobias)

High priority resolved:
- Permission labels in groups/[id] and groups/new now use Paraglide i18n keys
  (admin_perm_read_all, admin_perm_annotate_all, etc.) across de/en/es
- $derived used for permission arrays (reactive i18n) — Felix Svelte 5 rule
- UserGroup type in +layout.server.ts now uses generated API type (Markus/Felix)
- discardTarget annotation changed to variable-level type annotation

Accessibility (Leonie):
- EntityNav tablet icon strip buttons: min-h-[44px] for WCAG 2.5.8 compliance
- Flyout focus management: openFlyout() focuses first link, closeFlyout()
  returns focus to the trigger button that opened it
- Flyout animation replaced: broken inline style -> transition:fly={{ x: -160 }}

Tests (Sara/Felix):
- localStorage key assertion tests added per panel
- localStorage.removeItem calls updated to use the panel-specific keys
- page.server.spec.ts added for groups/[id] and tags/[id] delete actions
- Polling lifecycle tests added to system/page.svelte.spec.ts

Note: Paraglide types for new admin_perm_* keys regenerate automatically on
next npm run dev (Vite plugin). No manual compilation step needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 11:23:27 +02:00
Marcel
09d8fb5f95 feat(admin): add READ_ALL and ANNOTATE_ALL to groups permission matrix
Some checks failed
CI / Unit & Component Tests (push) Failing after 6m39s
CI / Backend Unit Tests (push) Failing after 3m7s
CI / E2E Tests (push) Failing after 1h41m58s
CI / Unit & Component Tests (pull_request) Failing after 4m24s
CI / Backend Unit Tests (pull_request) Failing after 2m32s
CI / E2E Tests (pull_request) Failing after 1h43m50s
Adds 'Nur lesen' (READ_ALL) and 'Lesen & Annotieren' (ANNOTATE_ALL)
as standard permission options alongside the existing 'Lesen & Schreiben'
(WRITE_ALL), ordered from least to most access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 10:12:48 +02:00
Marcel
9996055cac feat(admin): mass import card on system tab with live status polling
Adds a new card on the System tab that triggers the existing
POST /api/admin/trigger-import endpoint. Status is polled every 2 s
while RUNNING and stops automatically on DONE or FAILED.
IDLE/RUNNING/DONE/FAILED states each render distinct UI feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:42:44 +02:00
Marcel
559b522507 feat(admin): entity flyout for tablet icon strip (Phase 9 complete)
Tapping any icon in the 48px tablet nav strip now opens a 160px overlay flyout
with full entity labels and navigation links. Flyout closes on Escape, backdrop
click, or link click. Includes role="dialog", aria-modal, aria-label for WCAG.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:06:03 +02:00
Marcel
3c54401bb2 feat(admin): responsive entity nav and collapsible list panels (Phase 9)
EntityNav: hidden on mobile, 48px icon strip at tablet (md), full labels+counts at desktop (lg).
Each list panel collapses to a 32px handle via localStorage-persisted state; auto-collapses when
navigating to the "+New" route. Mobile routing hides the list panel when a detail route is active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 07:19:41 +02:00
Marcel
06a489567a feat(admin): phase 8 — unsaved-changes guard on all detail panels
Add beforeNavigate + isDirty tracking to users/[id], users/new,
groups/[id], groups/new, and tags/[id] edit panels. When a user
navigates away with unsaved changes, the navigation is cancelled and
an inline amber warning banner appears with a Discard button that
resumes navigation. Saving successfully clears the dirty flag.

Add i18n key admin_unsaved_warning (de/en/es).
Add spec files for groups/[id] and tags/[id] panels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:58:10 +02:00
Marcel
fabb517d0b refactor(admin): phase 7 — delete old tab components and page.server.ts
Remove UsersTab, GroupsTab, TagsTab, SystemTab and their specs; delete
the monolithic +page.server.ts with shared load + 6 form actions (all
now handled by dedicated sub-route servers under users/, groups/, tags/).
Add delete action and confirmation button to user edit panel.
Fix test to query the edit form by id rather than the first form in DOM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:44:52 +02:00
Marcel
cee16c1657 feat(admin/system): add system maintenance page under /admin/system
Moves the system maintenance panel out of the old tab-based admin page
and into a dedicated route. Renders maintenance cards with spinner state
and success message on completion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:39:09 +02:00
Marcel
908173de97 feat(admin/tags): add tags entity with master-detail sub-routes and type-to-confirm delete
Creates the full tags section under /admin/tags/:
- +layout.server.ts: loads tags list via GET /api/tags
- TagsListPanel.svelte: left list panel (name, active state)
- +layout.svelte: composes list panel + children slot
- +page.svelte: empty selection prompt
- [id]/+page.server.ts: rename (PUT) and delete actions
- [id]/+page.svelte: rename form + danger zone with type-to-confirm delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:33:33 +02:00
Marcel
8197db2c14 feat(admin/groups): add groups entity with master-detail sub-routes
Creates the full groups section under /admin/groups/:
- +layout.server.ts: loads groups list via GET /api/groups
- GroupsListPanel.svelte: left list panel (name + permission count, active state)
- +layout.svelte: composes list panel + children slot
- +page.svelte: empty selection prompt
- [id]/+page.server.ts: update (PATCH) and delete actions
- [id]/+page.svelte: edit detail panel with Standard/Administrative permission sections
- new/+page.svelte and +page.server.ts: create group form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:26:45 +02:00
Marcel
c8a834b91b feat(admin): add layout server auth guard and Phase 1 hotfixes
- +layout.server.ts: auth guard (throws 403 for non-admin) with granular
  permission flags and entity counts for EntityNav
- GroupsTab: add ⚙ prefix to ADMIN badge (WCAG 1.4.1, non-color indicator)
- TagsTab: remove opacity-0 from action buttons (hidden on touch devices)
- +layout.svelte: remove unused isSystem derived

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:10:51 +02:00
Marcel
8fc360a596 fix(admin): guard GET /api/users/{id} with @RequirePermission(ADMIN_USER)
Fixes IDOR: the endpoint was publicly accessible to any authenticated user.
Now requires ADMIN_USER permission, matching all other user management endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 01:09:40 +02:00
Marcel
16101240f1 chore: resolve merge conflicts with main
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 2m32s
CI / Backend Unit Tests (pull_request) Failing after 2m17s
CI / E2E Tests (pull_request) Failing after 2h43m0s
CI / Backend Unit Tests (push) Failing after 14m52s
CI / E2E Tests (push) Failing after 3h14m47s
Kept our version of accessibility.spec.ts (color-contrast rule enabled,
exclusion comment removed) over main's disabled version — the contrast
fixes in this branch make the exclusion unnecessary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:51:32 +01:00
Marcel
b5580b0b24 fix(#147): replace text-accent with text-primary on all text elements
--c-accent (#a1dcd8 light / #00c7b1 dark) is a decorative mint token —
1.52:1 on white, nowhere near WCAG AA. Every place it appeared as the
colour of a text label or interactive button is switched to text-primary
(#012851, 16.8:1 on white) with hover:text-ink-2 for consistency.

Affected: UsersTab, GroupsTab, CommentThread (Reply), DocumentList
(Clear search), PdfViewer (Direkt öffnen link).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 18:23:37 +01:00
Marcel
2b0f467213 i18n: translate page titles (home, persons, admin, login, error)
Some checks failed
CI / Backend Unit Tests (pull_request) Waiting to run
CI / E2E Tests (pull_request) Waiting to run
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
Replaces hardcoded German strings with Paraglide message keys
(page_title_home/persons/admin/login/error) across de/en/es.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 18:05:48 +01:00
Marcel
9a4e088de9 fix(#118): resolve wcag2a/wcag2aa violations found by axe-core suite
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
- Add <svelte:head><title> to home, persons, admin, login, and error pages
- Add aria-label to hidden file input in DropZone (sr-only but must be labelled)
- Add aria-label to search input in SearchFilterBar
- Create +error.svelte so error pages always have a document title
- axe-core spec: add buildAxe() helper, disable color-contrast (brand palette, tracked separately)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 17:29:47 +01:00
Marcel
3983771e79 test(#123): add Vitest integration tests for SvelteKit load functions
Adds server-project spec files for the four priority routes:
- routes/+page.server (home/search) — happy path, 401 redirect, network error fallback
- routes/documents/[id]/+page.server — happy path, comments fetch failure, 401/403/404
- routes/persons/[id]/+page.server — happy path, partial API failure, 403/404
- routes/admin/+page.server — ADMIN permission gate (none/read-only/undefined/no groups)

All tests run in Node environment with vi.mock() for createApiClient and
$env/dynamic/private. No real network calls; total suite runs in < 1 second.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 16:31:49 +01:00
Marcel
4ff87b035e fix: use bind:group in UserGroupsSection to prevent admin permission loss
Replaced one-way checked={...} with bind:group={selected} driven by a
writable $derived. In Svelte 5, the $derived pattern guarantees the DOM
checked state is always in sync at FormData capture time, so groupIds
is never accidentally sent as [] when the admin edits their own profile.

Sending groupIds:[] causes adminUpdateUser to clear all groups, which
revokes the admin's own permissions on the next request.

Tests: UserServiceTest (+4 for adminUpdateUser group behaviour),
page.svelte.spec.ts (+1 FormData assertion at submit time).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:42:03 +01:00
Marcel
1de4f8a605 fix(ui): hide logo on mobile+tablet, fix admin tab overflow
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
- AppNav: hide entire logo div (incl. mr-10 margin) below md: breakpoint
  to eliminate the phantom whitespace left of the hamburger button
- admin: 2×2 grid on mobile → flex row at sm:, so "Schlagworte" fits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 17:00:56 +01:00
Marcel
fcff7fbdb1 fix(#94): replace text-white with text-primary-fg on all primary buttons
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
In dark mode --c-primary switches from navy (#012851) to mint (#a1dcd8).
Buttons using bg-primary+text-white showed white text on mint at 1.4:1
contrast — invisible. bg-brand-navy buttons were also invisible (navy on
near-black canvas, 1.3:1).

Replaced in 28 components app-wide:
- bg-primary ... text-white → text-primary-fg
- hover:bg-primary hover:text-white → hover:text-primary-fg
- bg-brand-navy ... text-white + hover:bg-brand-navy/90 →
  bg-primary ... text-primary-fg + hover:bg-primary/90

Light mode is unchanged: primary-fg = white in light mode.
Dark mode: primary-fg = navy (#012851) on mint bg = readable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:07:37 +01:00
Marcel
afebaf4c53 fix(#91): add px-4 base padding and fix admin tab overflow at 320px
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 2m42s
CI / Backend Unit Tests (pull_request) Successful in 2m23s
CI / E2E Tests (pull_request) Failing after 29m0s
CI / Unit & Component Tests (push) Successful in 3m20s
CI / Backend Unit Tests (push) Successful in 2m21s
CI / E2E Tests (push) Failing after 29m37s
Home and Admin had no horizontal padding below the sm breakpoint (640px),
causing content to bleed to viewport edges. Admin's flex justify-between
row with h1 + 4 tab buttons overflowed by ~110px at 320px.

- +page.svelte: add px-4 to <main> (sm:px-6 lg:px-8 unchanged)
- admin/+page.svelte: add px-4 to outer container; stack header row
  vertically on mobile (flex-col sm:flex-row); reduce tab button padding
  to px-2 on mobile (sm:px-4 on desktop)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 15:50:03 +01:00
Marcel
25014cce2d refactor(admin/users): extract user form sub-components
Shared (src/lib/components/user/):
- UserProfileSection.svelte: name/birth-date/email/contact fields
- UserGroupsSection.svelte: group checkboxes
- UserPasswordSection.svelte: new/confirm password fields

Page-local:
- admin/users/new/AccountSection.svelte: username + initial password

admin/users/[id] drops from 224 → ~35 lines.
admin/users/new drops from 191 → ~30 lines.
Date utilities imported from \$lib/utils/date.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 12:17:55 +01:00
Marcel
af59ed4de4 refactor(admin): split admin page into tab sub-components
Split admin/+page.svelte (573 lines) into:
- UsersTab.svelte: user table with delete action
- TagsTab.svelte: tag list with inline rename and delete
- GroupsTab.svelte: groups table with inline edit + create form
- SystemTab.svelte: backfill buttons with own state

Page drops from 573 → ~40 lines.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 12:13:06 +01:00
Marcel
162c58e8c5 fix(components): replace remaining unthemed gray classes with semantic tokens
Replace text-gray-*, bg-gray-*, border-gray-*, divide-gray-*, placeholder-gray-*,
focus:border-blue-*, focus:ring-blue-*, hover:bg-blue-*, and ring-brand-mint with
their semantic-token equivalents (text-ink, bg-muted, border-line, etc.) across
all pages and shared components so dark mode renders correctly everywhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:47:56 +01:00
Marcel
e4539ed0f0 refactor(components): replace all hardcoded colors with semantic tokens
Replaces bg-white, text-brand-navy, border-brand-sand, text-gray-*, bg-[#2A2A2A],
bg-brand-purple/15, hover:bg-brand-sand, etc. across all 35 .svelte files with
semantic token utilities (bg-surface, text-ink, border-line, bg-pdf-bg, bg-nav-active,
bg-muted, text-accent, bg-primary, ...).

Also adds CSS filter: invert(1) in layout.css for De Gruyter <img> icons in dark mode,
excluding icons that carry .invert already (to prevent double-inversion).

Closes #64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:47:56 +01:00
Marcel
e83ba9b681 style(frontend): apply Prettier formatting to 26 pre-existing files
No logic changes — whitespace and indentation only. These were flagged
by the pre-commit hook when running lint after layout.css was modified.

Refs #64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:47:56 +01:00
Marcel
0ec86220d3 feat(backend): add POST /api/admin/backfill-file-hashes endpoint
- DocumentRepository: findByFileHashIsNullAndFilePathIsNotNull()
- AnnotationRepository: findByDocumentIdAndFileHashIsNull()
- FileService: downloadFileBytes() downloads raw bytes from S3 for hashing
- AnnotationService: backfillAnnotationFileHashForDocument() sets hash on null-hash annotations
- DocumentService: backfillFileHashes() iterates documents with null hash,
  downloads bytes, computes SHA-256, saves doc, then propagates hash to annotations
- AdminController: POST /api/admin/backfill-file-hashes delegates to DocumentService

Closes #56

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 17:32:29 +01:00
Marcel
b45ec744b2 feat: add PDF annotation feature (#40)
Backend:
- Add ANNOTATE_ALL permission
- Add ANNOTATION_NOT_FOUND and ANNOTATION_OVERLAP error codes
- V10 migration: document_annotations table with page/rect/color/owner
- DocumentAnnotation entity, AnnotationRepository, CreateAnnotationDTO
- AnnotationService: overlap detection (rectangle intersection), ownership enforcement on delete
- AnnotationController: GET (authenticated), POST/DELETE (ANNOTATE_ALL)
- 15 new tests (AnnotationServiceTest, AnnotationControllerTest) — TDD red/green

Frontend:
- AnnotationLayer.svelte: pointer-event drawing, colored rect overlays, delete buttons
- PdfViewer.svelte: annotate toggle, color picker, loads/saves/deletes annotations via API
- Disabled annotate button with tooltip for users without ANNOTATE_ALL
- canAnnotate exposed from layout server, passed to PdfViewer
- errors.ts + de/en/es translations for new error codes
- 3 new unit tests for AnnotationLayer — TDD red/green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:27:21 +01:00
Marcel
47b8cc9340 feat(frontend): add System tab to admin panel with backfill-versions action
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) Successful in 2m13s
CI / Backend Unit Tests (pull_request) Successful in 2m17s
CI / E2E Tests (pull_request) Failing after 22m45s
Admin can trigger an initial history snapshot for all documents without
version history. Shows count of backfilled documents after completion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:33:39 +01:00
Marcel
70d858b65a fix(tests): add missing user/canWrite/form props to admin spec fixtures
After the layout load function started injecting user+canWrite into all
page data, the admin spec files failed svelte-check with missing property
errors. Add user:undefined, canWrite:true, and form:null to all fixture
data objects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:01:25 +01:00
Marcel
fb4f8e820c feat(admin): add dedicated routes for admin user management (#37)
Some checks failed
CI / Unit & Component Tests (push) Successful in 2m4s
CI / Backend Unit Tests (push) Successful in 1m59s
CI / E2E Tests (push) Failing after 18m4s
CI / Unit & Component Tests (pull_request) Successful in 2m2s
CI / Backend Unit Tests (pull_request) Successful in 2m0s
CI / E2E Tests (pull_request) Failing after 16m10s
- New GET /admin/users/new page: create user with all profile fields
  (login, password, firstName, lastName, birthDate, email, contact, groups)
- New GET /admin/users/[id] page: edit user profile, groups, and
  optional password change without requiring current password
- New PUT /api/users/{id} backend endpoint (ADMIN_USER permission)
  with AdminUpdateUserRequest DTO for admin-override user updates
- Refactored admin users tab: replaced inline editing with edit links
  to dedicated routes; create button now links to /admin/users/new
- Extended CreateUserRequest with profile fields so new users can be
  created with full profile data in a single request
- Added 28 component tests across 3 new spec files (TDD)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 16:33:50 +01:00
Marcel
db2fc33e99 fix(frontend): enforce lint locally and in CI, fix all pre-existing violations
Some checks failed
CI / Unit & Component Tests (push) Successful in 1m59s
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
## Pre-commit hook
- Add .husky/pre-commit at repo root: runs `cd frontend && npm run lint`
- Update prepare script in package.json to auto-configure git hooks path
  on npm install (git -C .. config core.hooksPath .husky)
- Add lint step to CI unit-tests job so it catches issues before tests run
- Add generated dirs to .prettierignore (paraglide_bak*, test-results, .auth)
- Add src/lib/paraglide_bak* to .gitignore so ESLint can ignore them

## ESLint fixes (all pre-existing)
- Disable svelte/no-navigation-without-resolve: false positive in SvelteKit
  (rule targets Svelte 5 standalone routing, not SvelteKit <a href>)
- Fix svelte/require-each-key: add (item.id)/(item) keys to all {#each} blocks
  across 10 files — improves Svelte reconciliation performance
- Fix svelte/prefer-writable-derived in PersonTypeahead: $state+$effect → $derived
- Fix svelte/prefer-svelte-reactivity: URLSearchParams → SvelteURLSearchParams,
  Map → SvelteMap (enables Svelte reactive tracking)
- Fix @typescript-eslint/no-unused-vars: remove dead imports/variables

## Prettier
- Run npm run format to bring all source files in line with .prettierrc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 15:55:42 +01:00
Marcel
4771832492 refactor(frontend): extract toActionResult helper and formatDate utility
- Admin page: replace 7 identical error-handling blocks with a single
  toActionResult() helper — DRY without over-abstraction
- New date.ts util: formatDate(isoDate) centralises the T12:00:00
  timezone guard and Intl.DateTimeFormat locale config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:10:04 +01:00
Marcel
20313de4e9 feat(i18n): fix remaining hardcoded strings and add login page switcher
- Add 9 missing translation keys to de/en/es.json:
  doc_file_error_preview, doc_download_title, doc_tag_filter_title,
  doc_conversation_title, doc_preview_iframe_title, doc_image_alt,
  doc_no_date, person_merge_will_be_deleted, admin_user_delete_confirm

- documents/[id]/+page.svelte: replace 6 hardcoded strings with m.*()
- persons/[id]/+page.svelte: replace "wird gelöscht." and "Kein Datum"
- admin/+page.svelte: replace confirm() string with m.admin_user_delete_confirm()
- login/+page.svelte: add top-right DE/EN/ES language switcher (Option B)
  and wire existing login_* keys to the form labels

Closes #12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:34:40 +01:00
Marcel
0e76be5672 feat: implement i18n — extract all UI strings, add EN + ES-MX translations, add language selector
Some checks failed
CI / Unit & Component Tests (push) Successful in 9m36s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / E2E Tests (push) Failing after 14m41s
Extract all hardcoded German strings from every .svelte file and component
into Paraglide message keys. Add complete translations for all keys in
messages/en.json (English) and messages/es.json (Spanish/Mexico).

Changes:
- messages/de.json: 100+ keys covering navigation, buttons, form labels,
  placeholders, section headings, empty states, and error messages
- messages/en.json, messages/es.json: complete translations for all keys
- project.inlang/settings.json: change baseLocale from "en" to "de"
- +layout.svelte: add DE/EN/ES language selector in header using setLocale();
  active language is bold, choice persists via Paraglide cookie strategy
- All 10 route pages + 3 shared components: replace hardcoded German with m.key()
- e2e/lang.spec.ts: E2E tests for language selector visibility, switching,
  persistence across navigation, and active state highlighting

Closes #2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 15:13:56 +01: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
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
62189d8bb3 fix: show names in admin tag panel 2026-03-15 21:08:06 +00:00
Marcel
d76248cffd refactor: migrate all page.server.ts files to typed API client
All server-side fetch calls now go through createApiClient() from
$lib/api.server.ts, which wraps openapi-fetch with the generated OpenAPI
types. This means backend changes are reflected in the frontend after
running npm run generate:api.

- Add stub src/lib/generated/api.ts (replaced by generate:api output)
- Fix GroupController: missing /api prefix and ResponseStatusException
- Root, conversations, persons, documents pages all use typed client
- Error handling uses apiError.code directly (no parseBackendError needed)
- Edit page load uses typed client; PUT action keeps raw fetch (multipart)
- Login keeps raw fetch (explicit Authorization header, not cookie auth)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 13:39:15 +01:00
Marcel
4cc86de143 feat: replace raw error messages with structured error codes
Backend now returns { code: ErrorCode, message: string } for all errors,
making it language-agnostic. Frontend maps codes to localised strings via
Paraglide (en/de/es), so translations live in messages/*.json.

- Add ErrorCode enum and DomainException with static factory methods
- Update GlobalExceptionHandler to return ErrorResponse(code, message)
- Replace ResponseStatusException throughout controllers/services/aspects
- Add frontend errors.ts with parseBackendError() and getErrorMessage()
- getErrorMessage() delegates to Paraglide m.error_*() functions
- Add error_* keys to messages/en.json, de.json, es.json
- Update all page.server.ts files to use the new error utilities
- Fix hardcoded localhost URLs in admin and login pages
- Fix missing baseUrl in deleteTag action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 13:15:28 +01:00
Marcel
e63adb964d restructure: flatten workspace nesting, move devcontainer to root
- backend/workspaces/backend/ → backend/
- backend/workspaces/frontend/ → frontend/
- backend/.devcontainer/ + .vscode/ → repo root (where VS Code expects them)
- loose scripts/SQL files → scripts/
- replace nested git repo with single repo at project root
- update docker-compose.yml build context and devcontainer.json path
- add root .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 11:47:58 +01:00