SVG icons are black by default; on the navy primary button they need
invert in light theme (white icon) and invert-0 in dark theme (dark
icon on lighter button background).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Load /api/stats in parallel; PersonsStatsBar shows totals; person cards
show alias, life date range, and document count badge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move text-decoration-thickness/underline-offset into the global a:hover
base rule so every link that shows an underline on hover gets identical
treatment: 2px thick, 4px offset, accent colour.
Remove the now-redundant per-component decoration-brand-mint / decoration-
accent / decoration-2 / underline-offset-{2,4} utilities from DocumentList,
enrich, persons, PersonDocumentList, and PanelMetadata.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
text-ink/60 produces an opacity-blended colour whose contrast is
background-dependent: it passes on white (4.8:1) but fails on the sandy
canvas #f0efe9 (3.97:1, below WCAG AA 4.5:1). Replace every occurrence
with text-ink-2 (#4b5563, 6.6:1 on canvas — WCAG AA ✓).
Also adds a warning comment above --c-accent in layout.css to prevent
the text-accent misuse from recurring.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
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>
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>
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>
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>
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>
## 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>
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>
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>
- 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>
- 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>
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>