Commit Graph

82 Commits

Author SHA1 Message Date
Marcel
401a1f359f feat(frontend): replace logout button with user avatar dropdown in nav
Show user initials (e.g. MM) in a circular button when name is set,
or a fallback person icon. Clicking opens a dropdown with links to
/profile and a logout form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:03:42 +01:00
Marcel
82c8401167 feat(frontend): add i18n messages and error codes for profile feature
Add profile_* message keys for the profile page forms in de/en/es.
Add EMAIL_ALREADY_IN_USE and WRONG_CURRENT_PASSWORD to ErrorCode type and
getErrorMessage switch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:03:19 +01:00
Marcel
da0d5495d0 fix(persons): prevent stale navigation from clobbering focused search input
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 2m12s
CI / Backend Unit Tests (pull_request) Successful in 1m58s
CI / E2E Tests (pull_request) Successful in 17m40s
CI / Unit & Component Tests (push) Successful in 1m58s
CI / Backend Unit Tests (push) Successful in 1m59s
CI / E2E Tests (push) Successful in 14m56s
The persons list search input used value={data.q || ''} bound directly to
server data, so every navigation completion would reset it to the URL value
mid-typing, dropping keystrokes just like issue #34 on the home page.

Apply the same focus-guard fix: introduce local `q` state, a `qFocused`
flag, and a guarded $effect that only syncs URL → state when the input is
not focused. Adds a regression test matching the home-page pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:54:56 +01:00
Marcel
513a7290b0 fix(conversations): keep swap button in DOM to prevent grid column width shift
All checks were successful
CI / Unit & Component Tests (push) Successful in 1m56s
CI / Backend Unit Tests (push) Successful in 1m53s
CI / E2E Tests (push) Successful in 16m37s
CI / Unit & Component Tests (pull_request) Successful in 2m2s
CI / Backend Unit Tests (pull_request) Successful in 2m2s
CI / E2E Tests (pull_request) Successful in 16m55s
The swap button was conditionally removed from the DOM with {#if}, which
caused the receiver input to collapse into the narrow auto column of the
grid-cols-[1fr_auto_1fr] layout on desktop when no persons were selected.

The button is now always rendered. On desktop it becomes invisible
(visibility:hidden) when no persons are selected, preserving the middle
column width so both 1fr columns stay equal. On mobile it remains hidden
(display:none) via the hidden class so no empty gap appears between the
stacked inputs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
0f8b582813 test(documents): add component tests for sender/receiver URL prefill on new document page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
4026bb9003 feat(documents): prefill sender and receiver from URL params on new document page
When navigating from the conversations page via the 'New document in this
correspondence' link, the senderId and receiverId query params are now read
in the server load, resolved to person names, and used to pre-populate the
sender typeahead and receiver multi-select on the form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
f2f9a1bf03 test(conversations): add component tests for new features
Covers: empty state, swap button (visible/hidden, goto called with
swapped params), summary content, year dividers, and new document link
visibility gated by canWrite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
76031de8eb fix(conversations): restore {#if} guard on swap button
The guard was lost when the button was moved into the grid between the
two person inputs. Without it the button rendered even when no persons
were selected, breaking the UX and the E2E assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
e2874528cd fix(conversations): hide new document link for read-only users
The link navigates to a page that requires WRITE_ALL. Guard it with
data.canWrite (supplied by the layout) so read-only users never see a
link that leads to a 403.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
aa127de9bd refactor(conversations): move swap button between person input fields
On desktop the button sits between the two typeaheads as an icon-only
button (icon rotated 90° to point left/right) aligned to the input
baseline. On mobile it renders full-width with the label text between
the stacked fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
65a8048e25 feat(conversations): add new document link pre-filled with both persons (#33)
Adds a link next to the summary that navigates to the new-document form
with senderId and receiverId pre-filled from the current conversation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
1ab063486c feat(conversations): add year dividers between documents (#30)
Renders a horizontal rule with the year label between consecutive
documents that belong to different years.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
0a1075e03f feat(conversations): add summary with document count and year range (#31)
Shows a summary line above the conversation listing with total document
count and the year span, e.g. "4 Dokumente · 1923–1965".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
ca212e871f feat(conversations): add swap button (#32)
Adds a button between the two person typeaheads that swaps sender and
receiver, then reloads the conversation view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:32:23 +01:00
Marcel
0525e66d55 feat(conversations): filter person typeahead to correspondents of selected person
All checks were successful
CI / E2E Tests (push) Successful in 18m17s
CI / Unit & Component Tests (push) Successful in 3m37s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / Unit & Component Tests (pull_request) Successful in 2m12s
CI / Backend Unit Tests (pull_request) Successful in 2m1s
CI / E2E Tests (pull_request) Successful in 15m17s
Closes #29

Backend:
- Add PersonRepository.findCorrespondents / findCorrespondentsWithFilter
  (native SQL, orders by shared document count DESC, limit 10)
- Add PersonService.findCorrespondents(personId, q) delegating to the
  correct repository method based on whether a query string is present
- Expose GET /api/persons/{id}/correspondents?q= in PersonController

Frontend:
- Add optional restrictToCorrespondentsOf prop to PersonTypeahead
- On focus with the prop set, fetch correspondents immediately (no typing
  required) — opens the dropdown showing top correspondents
- On input with the prop set, hit the correspondents endpoint with q= param
- Without the prop, keep existing /api/persons?q= behaviour unchanged
- Wire the prop bidirectionally in /conversations: sender restricts receiver
  and vice versa

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:23:11 +01:00
Marcel
acf6fc05ad fix(search): prevent stale navigation from clobbering focused search input
Some checks failed
CI / E2E Tests (push) Waiting to run
CI / Unit & Component Tests (push) Successful in 10m14s
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (pull_request) Failing after 13m49s
CI / Backend Unit Tests (pull_request) Failing after 13m59s
CI / Unit & Component Tests (pull_request) Failing after 14m9s
The sync $effect on the home page unconditionally overwrote the local `q`
state with the URL value after every navigation. When users typed faster
than a navigation round-trip (debounce fires → goto() → data reloads),
the completed navigation wrote the stale URL value back into the input,
dropping the characters typed in the interim.

Guard the `q` assignment in the effect with a `qFocused` flag (set via
onfocus/onblur on the text input). Covers issue #34.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 20:57:23 +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
28dea45cc3 fix(i18n): remove trailing comma from all three message files
All checks were successful
CI / Unit & Component Tests (push) Successful in 1m39s
CI / Backend Unit Tests (push) Successful in 1m51s
CI / E2E Tests (push) Successful in 16m21s
Standard JSON does not allow trailing commas. The comma after the last
key in de/en/es.json caused paraglide to fail compilation, which meant
messages.js was never generated and all component tests crashed on import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:33:55 +01:00
Marcel
11f6f9e2a2 refactor(frontend): apply formatDate utility and fix derived/error handling
Some checks failed
CI / Unit & Component Tests (push) Failing after 1m37s
CI / Backend Unit Tests (push) Successful in 2m2s
CI / E2E Tests (push) Has started running
- Replace 5 inline Intl.DateTimeFormat blocks with formatDate() across
  home, conversations, persons detail, and document detail pages
- Fix coCorrespondents: $derived(() => ...) → $derived.by(...) —
  the old form typed the value as a function, breaking template call sites
- Persons list: throw error on API failure instead of silently returning []

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:11: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
761c903111 refactor(person): remove redundant conversations link from header
Some checks failed
CI / Unit & Component Tests (push) Failing after 1m17s
CI / Backend Unit Tests (push) Successful in 1m52s
CI / E2E Tests (push) Has started running
The co-correspondent chips already link directly to the conversation view
pre-filled with both persons, making the generic "Konversationen anzeigen"
header link redundant. Removed the link and the person_btn_conversations
i18n key from all three locales.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:27:25 +01:00
Marcel
931a8dac95 refactor(person): move sort button into each section heading, sort independently
Some checks are pending
CI / Unit & Component Tests (push) Successful in 1m43s
CI / Backend Unit Tests (push) Successful in 1m57s
CI / E2E Tests (push) Has started running
Replaced the single shared sort control with per-section sort buttons placed
inline in each heading row (right-aligned via ml-auto). Each section now sorts
independently, which matches user expectation and keeps the control visually
anchored to the list it affects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:22:56 +01:00
Marcel
3f717e3266 refactor(person): fold year range into section headings, remove standalone stats bar
Some checks are pending
CI / Unit & Component Tests (push) Successful in 1m39s
CI / Backend Unit Tests (push) Successful in 2m8s
CI / E2E Tests (push) Has started running
The floating stats bar was visually disconnected and showed a combined document
count already visible from the per-section badges. Replaced it with a year range
shown inline next to each section heading (e.g. "Gesendete Dokumente · 12 · 1921–1945"),
making the range contextually relevant per direction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:16:22 +01:00
Marcel
203b7d2b08 fix(auth): proxy document file requests server-side to prevent Basic Auth popup
Some checks are pending
CI / Unit & Component Tests (push) Successful in 1m55s
CI / Backend Unit Tests (push) Successful in 2m8s
CI / E2E Tests (push) Has started running
Client-side fetch('/api/documents/{id}/file') bypassed the handleFetch hook
that injects the Authorization header, causing the browser to receive a 401
with WWW-Authenticate: Basic and show a native auth dialog.

Added a SvelteKit server route at /api/documents/[id]/file that proxies the
request through the server, where handleFetch injects the auth cookie correctly.

Also fixed E2E default password (admin → admin123) to match application.yaml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:01:31 +01:00
Marcel
e9b03ee6a9 fix(person): add i18n for show-more button and limit doc lists to 5
All checks were successful
CI / Unit & Component Tests (push) Successful in 1m59s
CI / Backend Unit Tests (push) Successful in 2m9s
CI / E2E Tests (push) Successful in 16m33s
- Add person_show_more key (DE/EN/ES)
- Limit sent/received document lists to 5 with a translated "show more" button
- Co-correspondent chips now link to /conversations?senderId=...&receiverId=...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:13:38 +01:00
Marcel
ba04e62f87 fix(person): remove redundant role badges from document lists
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
The Gesendet/Empfangen badge is redundant since documents already appear
in separate Gesendete/Empfangene sections.

Refs #21
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:05:19 +01:00
Marcel
fa4bfb8e5c feat(routes): add server-side WRITE_ALL guard on write-only routes
Block direct URL navigation to /persons/new, /documents/new,
/documents/:id/edit for users without WRITE_ALL permission.
E2E tests verify admin user retains access to all write routes.

Closes #17
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:47:52 +01:00
Marcel
fde75f3fcf feat(ui): hide write UI from users without WRITE_ALL permission
Wrap write-only elements with {#if data.canWrite} in:
- Home page: Neues Dokument link
- Persons list: Neue Person link
- Document detail: Bearbeiten button
- Person detail: edit button, edit form, merge section

Refs #17
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:47:45 +01:00
Marcel
03a1a86cdb feat(layout): expose canWrite flag from layout server load
Derives canWrite from WRITE_ALL permission in user groups, available
as page.data.canWrite on every page without per-page boilerplate.

Refs #17
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:47:37 +01:00
Marcel
55ffaa1c5c feat(person): show received docs, role badges, stats bar, co-correspondents
- Split document list into Gesendete / Empfangene Dokumente sections
- Add role badges (Gesendet / Empfangen) on each document card
- Add statistics strip showing total count and year range
- Add co-correspondents section with frequency-sorted chips
- Single sort toggle applies to both sections

Closes #1 Closes #19 Closes #21 Closes #22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:39:50 +01:00
Marcel
1fdde95b09 feat(frontend): load sent and received documents for person detail
Split single documents load into sentDocuments and receivedDocuments,
fetched in parallel via Promise.all.

Refs #1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:39:37 +01:00
Marcel
c056d804e6 feat(i18n): add translations for received docs, role badges, and co-correspondents
Add keys: person_received_docs_heading, person_no_received_docs,
person_role_sender, person_role_receiver, person_co_correspondents_heading
in DE, EN, ES.

Refs #1 Refs #21 Refs #22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:39:30 +01:00
Marcel
490382b5de feat(api): regenerate TypeScript types with received-documents endpoint
Refs #1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:39:24 +01:00
Marcel
c3f487f16c fix(e2e+i18n): add missing DE translation, fix E2E test selectors
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
- Add missing person_btn_conversations translation to de.json
- Fix birth/death year test: exclude /persons/new link + wait for hydration
- Fix lang test switching back to DE: wait for hydration + clear locale cookie
  (headless Chromium doesn't reliably delete cookies via document.cookie)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:45:56 +01:00
Marcel
041bbdc2e6 merge(feat/person-birth-death-years): resolve conflicts with main, bump migration to V6
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 1m45s
CI / Backend Unit Tests (pull_request) Successful in 2m4s
CI / E2E Tests (pull_request) Failing after 18m40s
CI / Unit & Component Tests (push) Successful in 1m57s
CI / Backend Unit Tests (push) Successful in 2m13s
CI / E2E Tests (push) Failing after 18m39s
Resolves merge conflicts with main (feat/person-notes merged first).
Combines both features: birth/death years and notes field on person detail.
Renames migration V5__add_birth_death_years to V6 to avoid Flyway conflict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:04:44 +01:00
Marcel
08f7ae9a5c feat(persons): add notes field to person profile (issue #23)
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
V5 Flyway migration adds TEXT notes column; Person entity, service, and
controller updated to persist notes. Frontend edit form adds textarea and
view mode renders the notes section. Backed by 2 new service unit tests
(persist + blank clears).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:57:05 +01:00
Marcel
c01a07bd82 fix(e2e): fix conversations link test — exclude /persons/new and use specific link text
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 9h3m35s
CI / Backend Unit Tests (pull_request) Successful in 3m51s
CI / E2E Tests (pull_request) Failing after 17m8s
CI / Unit & Component Tests (push) Successful in 1m52s
CI / Backend Unit Tests (push) Successful in 2m3s
CI / E2E Tests (push) Failing after 16m23s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:48:58 +01:00
Marcel
cccc12429c chore: resolve merge conflict with main (keep both sort and conversations tests)
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 1m48s
CI / Backend Unit Tests (pull_request) Successful in 2m8s
CI / E2E Tests (pull_request) Failing after 3m40s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:46:47 +01:00
Marcel
b07391541b feat(persons): add birth/death year fields (issue #18)
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 1m48s
CI / Backend Unit Tests (pull_request) Successful in 2m3s
CI / E2E Tests (pull_request) Failing after 17m10s
V5 Flyway migration adds birth_year and death_year INTEGER columns.
Service validates birthYear <= deathYear (400 otherwise). Frontend edit
form adds year number inputs; view mode renders * year / † year. Backed
by 3 backend service tests and 1 E2E test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:45:02 +01:00
Marcel
fb08eb30a4 feat(persons): add sort toggle to person document list (issue #24)
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 1m55s
CI / Backend Unit Tests (pull_request) Successful in 2m9s
CI / E2E Tests (pull_request) Successful in 18m8s
Extracted sortDocumentsByDate utility with full Vitest coverage (6 tests),
wired it into the person detail page with a DESC/ASC toggle button, and
added an E2E smoke test for the toggle interaction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:24:46 +01:00
Marcel
371d92f52a feat(person): add conversations quick-link (#20)
Some checks failed
CI / E2E Tests (push) Failing after 17m44s
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 1m51s
CI / Backend Unit Tests (pull_request) Successful in 2m3s
CI / E2E Tests (pull_request) Failing after 17m7s
Add a "Konversationen anzeigen" link to the person detail page header
that navigates to /conversations?senderId={id}, pre-filling the person
as Person A. Includes i18n in de/en/es and an E2E test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:19:36 +01:00
Marcel
fe9b4a9569 fix(e2e): fix locale cookie httpOnly and add hydration waits
Some checks failed
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
CI / Unit & Component Tests (push) Successful in 2m3s
CI / Backend Unit Tests (push) Successful in 2m13s
CI / E2E Tests (push) Successful in 17m49s
Paraglide's client-side setLocale writes the locale via document.cookie,
which silently fails for HttpOnly cookies. SvelteKit's cookies.set()
defaults to httpOnly: true, so locale switching never worked in tests.
Fix by setting httpOnly: false on the locale cookie (it's a UI preference,
not a credential — no security concern).

Add waitForSelector('[data-hydrated]') before any click that relies on
SvelteKit JavaScript event handlers. Without this, the click fires before
hydration and the onclick handler is not yet registered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:34:40 +01:00
Marcel
7988c62246 fix(e2e): fix strict mode violation and conversations sort toggle
- Add exact: true to all language button selectors in lang.spec.ts to
  prevent Playwright from matching "Abmelden" (contains "de") alongside
  the DE language button
- Fix goto in conversations applyFilters to use absolute path
  /conversations?... instead of relative ?... so URL updates correctly
- Set locale: 'de-DE' in playwright.config.ts so Accept-Language header
  is consistent and locale detection defaults to German during tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:34:40 +01:00
Marcel
a998ef4550 test(i18n): add unit tests for locale detection + extract to module
Extract detectLocale() from hooks.server.ts into src/lib/server/locale.ts
so it can be tested in isolation. Add 7 unit tests covering:
- German, English, Spanish browser preferences
- Fallback when primary language is unsupported
- Quality value (q=) ordering
- Fully unsupported language → null
- Empty Accept-Language header → null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:34:40 +01:00
Marcel
c4be2eb46e feat(i18n): detect browser language as default locale
On first visit (no PARAGLIDE_LOCALE cookie), parse the Accept-Language
request header and set the cookie to the best matching supported locale
(de/en/es). The user's manual choice via the switcher always takes
precedence since the detection is skipped when the cookie exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:34:40 +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
4e8de3658f fix(api): use API_INTERNAL_URL in tags and persons proxy routes
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 15:13:17 +01:00
Marcel
b6361e6cbc fix(auth): use API_INTERNAL_URL in userGroup hook
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 15:13:17 +01:00
Marcel
a3948b6a0f fix(test): add missing user, createdAt, updatedAt, and tag id fields to page spec fixtures
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 11m27s
CI / Backend Unit Tests (push) Successful in 2m11s
CI / E2E Tests (push) Failing after 56s
2026-03-19 12:51:35 +01:00