Commit Graph

16 Commits

Author SHA1 Message Date
Marcel
52827ccc87 feat(documents): hide timeline density widget below lg (#385)
Tablet (640–1024px) is exactly the iPad audience for transcribers.
At 240 monthly bars on an 800px column the bars fall to ~3.3px wide,
well below the 44×44 touch-target floor. Bumps the visibility class
from hidden sm:block to hidden lg:block and matches the page.ts
matchMedia gate to (min-width: 1024px). Closes Leonie's tablet
touch-target finding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:00:47 +02:00
Marcel
5d92f5a32b refactor(documents): rework timeline UX after live testing (#385)
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m29s
CI / OCR Service Tests (push) Successful in 48s
CI / Backend Unit Tests (push) Failing after 3m46s
CI / Unit & Component Tests (pull_request) Failing after 4m31s
CI / OCR Service Tests (pull_request) Successful in 38s
CI / Backend Unit Tests (pull_request) Failing after 3m31s
Replaces the discrete zoom-in button with a Graylog-style drag-to-zoom
range selector and adds X/Y axis labels so the chart is readable.

Drag interaction
- Pointerdown on a bar attaches document-level pointermove/pointerup/
  pointercancel listeners; pointermove maps clientX to a bar index via
  the row's bounding rect, so the mint-bordered window expands smoothly
  even when the cursor leaves the bar or the chart entirely.
- pointerup commits filter + zoom atomically. Same-bar release on a
  year bar (year-aggregated mode) zooms into that year's months;
  same-bar release on a month bar emits filter-only.
- setPointerCapture removed — it was suppressing pointerenter on
  sibling bars and preventing the drag window from expanding.
- Bar buttons are now h-full so the entire 80 px column is the hit
  target, not just the visible bar height.

Axis labels
- Y-axis: max-count and 0 labels left of the bar area.
- X-axis: tickIndicesFor() picks decadal years for long ranges, evenly
  spaced months for short year-zoom views, January boundaries for
  multi-year month ranges. formatTickLabel() drops the year when the
  visible range is a single year so 12-month zooms read "Jan Feb Mär…".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 08:54:48 +02:00
Marcel
a6123e1867 feat(documents): zoom-in tool for the timeline (#385)
Adds a zoom action that narrows the visible timeline range to the current
selection so the user can drill from year-level back into month-level
density. Zoom state lives in the URL (zoomFrom / zoomTo) so it survives
reload and is shareable.

- New `clipBucketsToRange(buckets, from, to)` helper applied before the
  >240-month year-aggregate decision, so a zoomed window flips back to
  month bars automatically when the clip narrows the range enough.
- `TimelineDensityFilter` gains `zoomFrom`, `zoomTo`, and `onzoomchange`
  props. Zoom button shown only when a selection exists and we aren't
  already zoomed; reset-zoom shown only when zoomed. Both placed in a
  shared right-edge action cluster alongside the × clear button.
- `+page.ts` reads zoomFrom/zoomTo from the URL and forwards them as
  props. `+page.svelte` extends FilterSnapshot + buildSearchParams, and
  triggerSearch accepts an optional zoom override so the onzoomchange
  callback can write the new pair (or clear them) atomically.
- 7 new component tests + 2 new page-integration tests cover the
  visibility rules and URL writes.
- 4 new unit tests for `clipBucketsToRange`.
- 3 new i18n keys (zoom in / zoom reset / drag aria-live) across de/en/es.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:23:38 +02:00
Marcel
6786c0112d feat(documents): wire TimelineDensityFilter into /documents/+page (#385)
Mounts the timeline above the result count, hidden on mobile via
\`hidden sm:block\` (defense-in-depth — +page.ts already gates the fetch).
The component's onchange callback updates local from/to and triggers
the existing search reload, so timeline selection composes with the
SearchFilterBar's other filters via AND semantics for free.

3 new page-level integration tests cover: widget renders when density
present, hides when null, and bar click navigates with correct
from/to URL params.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 22:16:05 +02:00
Marcel
567612761d refactor: move lib-root files to lib/shared/ and finalize domain structure
- Move api.server.ts, errors.ts, types.ts, utils.ts, relativeTime.ts to lib/shared/
- Move person relationship components to lib/person/relationship/
- Move Stammbaum components to lib/person/genealogy/
- Move HelpPopover to lib/shared/primitives/
- Update all import paths across routes, specs, and lib files
- Update vi.mock() paths in server-project test files
- Remove now-empty legacy directories (components/, hooks/, server/, etc.)
- Update vite.config.ts coverage include paths for new structure
- Update frontend/CLAUDE.md to reflect domain-based lib/ layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:53:31 +02:00
Marcel
efcc347c00 refactor: move shared components to lib/shared/ sub-packages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:40:14 +02:00
Marcel
e7f8aa5894 refactor: move document domain core to lib/document/
Moves ~25 components, utils (search, filename, groupDocuments,
documentStatusLabel, validateFile), bulkSelection store, and
TranscriptionSection sub-component. Fixes broken relative imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 13:56:36 +02:00
Marcel
a91ee1f26d refactor(documents): unify count + action links into one row
Some checks failed
CI / Unit & Component Tests (push) Failing after 3m7s
CI / OCR Service Tests (push) Successful in 32s
CI / Backend Unit Tests (push) Failing after 2m56s
Move result count, bulk-edit button, and new-document link into a shared
flex row so they appear on the same line. Adds an edit icon to the
bulk-edit button to visually match the existing plus icon on the add link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:41:24 +02:00
Marcel
7df00859c6 fix(bulk-edit): pluralization, edit-mode CTA, error UI, real loading state
Elicit C1+C3 — bulk-selection count uses ICU-style plural keys
(bulk_edit_n_selected_one / _other) so n=1 reads as "1 Dokument" instead
of "1 Dokumente". Save CTA in edit mode reads "Anwenden" via the existing
bulk_edit_save_button key; UploadSaveBar grew an editMode prop. Multi-
chunk progress text is now visible (not aria-only).

Felix C2 — bulk-edit page wires the backend error code through
parseBackendError + getErrorMessage instead of falling back to a generic
internal_error.

Felix C5 — editAllMatching no longer swallows fetch failures: the button
shows an inline error with the backend-mapped message (e.g. when the
filter cap is exceeded).

Leonie C8 — replace the literal "…" loading glyph on /documents/bulk-edit
with a spinner + role=status + aria-live=polite + visible "Loading
documents…" text.

Leonie C9 — partial-failure card and bulk-edit page error card now use
the design-system `text-danger` / `bg-danger/10` / `border-danger/40`
tokens (dark-mode safe) instead of raw red palette values.

Leonie C10 + C13 — German plural fixed; EN badges retensed
("+ added" → "+ will be added", "replaced" → "will replace") to match
the future-tense intent of DE/ES.

Refs #225, PR #331

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:46:58 +02:00
Marcel
156efe8b31 fix(bulk-edit): a11y + i18n hardening (Leonie blockers 1–4 + quick concerns)
B1 — i18n the archive-box / archive-folder labels and add helper text.
Karton/Mappe were hardcoded German and broke EN/ES locales (WCAG 3.1.2).

B2 — drop the hardcoded German aria-label on the onboarding callout.
role="note" + the visible localised text is self-describing; the redundant
label was overriding the translated content for AT users on EN/ES.

B3 — Escape clears the bulk selection while the bar is visible. Adds an
"Esc: Auswahl aufheben" hint visible at ≥ sm (WCAG 2.1.1).

B4 — /documents and /enrich reserve pb-32 when the bulk-selection bar is
visible so it doesn't occlude the last row or pagination (WCAG 1.4.10).

Folded in three Leonie quick-concerns:
 - C5: badge text-[10px] → text-[11px], raw text-gray-600 →
        design-token text-ink-2 (dark-mode safe)
 - C7: aria-live="polite" on bulk-selection-count
 - C11: "Alles aufheben" → "Auswahl aufheben" (DE/EN/ES) — disambiguates
        from "discard the operation entirely"

Refs #225, PR #331

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:35:40 +02:00
Marcel
d4f32ed5d4 feat(bulk-edit): add BulkSelectionBar and Alle-X-editieren fast path
- BulkSelectionBar component: sticky bottom bar shown only when canWrite
  and selection is non-empty. Buttons meet WCAG 44px touch targets and
  iOS safe-area inset is honoured.
- Bar mounted on /documents and /enrich.
- Alle X editieren button on /documents replaces the selection with
  every UUID matching the active filter (via /api/documents/ids) and
  jumps to /documents/bulk-edit.

Refs #225

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 15:07:26 +02:00
Marcel
8fa061187e refactor(documents): extract buildSearchParams — address @felixbrandt
triggerSearch (local state, filter change) and buildPageHref (server data,
page nav) were each iterating over the same ~10 filter params. Any new
filter would have had to land in two places. buildSearchParams is now the
single source of truth for which params the /documents URL understands;
both callers just pass their snapshot and an optional targetPage. (#316)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:20:24 +02:00
Marcel
78ac5d663d feat(documents): paginate search with a Pagination control
Frontend side of the /documents pagination work. The page.server.ts load
reads ?page= from the URL, forwards page+size=50 to the backend, and
exposes the new totalElements/pageNumber/pageSize/totalPages fields on
`data`. +page.svelte renders a <Pagination> component below the result
list; buildPageHref preserves every filter param and only updates page.
The existing triggerSearch debounce flow intentionally drops `page`
when any filter changes, so filter edits reset to page 0 automatically.

<Pagination> uses plain <a href> links (not goto) so SvelteKit's default
scroll restoration scrolls new pages to the top — the expected senior-UX
behaviour. Decorative chevrons wrapped in aria-hidden spans, 44px touch
targets, focus-visible ring, stacks vertically under 640px. The control
hides itself when totalPages ≤ 1.

Test coverage: 9 cases on Pagination (label, aria-current, prev/next
enable/disable, makeHref invocation, decorative chevron, touch target),
plus a filter-reset assertion on +page.svelte (page 5 → edit q →
goto URL must drop page=). Adds i18n keys in de/en/es. Manual edit to
api.ts pending a post-merge npm run generate:api against a rebuilt
dev backend. (#315)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:20:24 +02:00
Marcel
33ada55f12 feat(documents): restore sender/receiver grouping in document list
When sort=SENDER, documents group under the sender's display name card.
When sort=RECEIVER, a document appears under each receiver's card
(with multi-receiver duplication). Falls back to i18n labels for unknown
sender/receiver. Passes sort prop from /documents page to DocumentList.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:29:33 +02:00
Marcel
b2ea9e74fe fix(a11y): add sr-only h1 to documents page for screen reader landmark
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m42s
CI / OCR Service Tests (push) Successful in 34s
CI / Backend Unit Tests (push) Failing after 3m1s
CI / Unit & Component Tests (pull_request) Failing after 2m41s
CI / OCR Service Tests (pull_request) Successful in 34s
CI / Backend Unit Tests (pull_request) Failing after 2m48s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 00:25:16 +02:00
Marcel
10833fbe6b feat(frontend): add /documents page with search, filter, and year-card list
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m35s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 2m53s
CI / Unit & Component Tests (pull_request) Failing after 2m40s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
- New documents/+page.svelte wires SearchFilterBar + DocumentList with
  URL-driven navigation (goto + SvelteURLSearchParams)
- Reset button in SearchFilterBar now navigates to /documents
- Rename documents/+page.server.spec.ts → page.server.spec.ts to avoid
  SvelteKit route-file conflict on the + prefix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 00:06:25 +02:00