feat(geschichten): document filter chip — visible UI for ?documentId= filter #811

Merged
Owner

Closes #803

Summary

  • Loader — UUID-validates ?documentId=, fetches document title from GET /api/documents/{id} in parallel with person resolution; returns { id, title } | null. 403/404 fail-closed to { id, title: null } (no throw, no logging). Invalid UUIDs pass through to the list API unchanged (option B → backend 400).
  • DocumentFilterChip.svelte — new two-zone chip: Gefiltert nach Brief: prefix + font-serif italic title, line-clamp-2 sm:truncate sm:max-w-[16rem], separate ≥44 px touch-target remove button, aria-live="polite".
  • +page.svelte — chip wired into filter bar; emptyMessage extracted as $derived.by() with person-wins precedence; removeDocument navigation helper strips ?documentId= and preserves active person filters.
  • i18n — 3 new keys added to de/en/es: geschichten_filter_document_chip, geschichten_filter_remove_document_chip, geschichten_empty_for_document.

Test plan

  • 34 tests across 3 spec files all pass (npx vitest run --project=client src/routes/geschichten/... + --project=server)
  • npm run lint clean
  • Verify chip renders when navigating from a document detail page with ?documentId= in the URL
  • Verify removing the chip clears documentId but preserves active personId params
  • Verify empty-state shows "Noch keine Geschichten zu diesem Brief" when no results and document filter is active

🤖 Generated with Claude Code

Closes #803 ## Summary - **Loader** — UUID-validates `?documentId=`, fetches document title from `GET /api/documents/{id}` in parallel with person resolution; returns `{ id, title } | null`. 403/404 fail-closed to `{ id, title: null }` (no throw, no logging). Invalid UUIDs pass through to the list API unchanged (option B → backend 400). - **DocumentFilterChip.svelte** — new two-zone chip: `Gefiltert nach Brief:` prefix + `font-serif italic` title, `line-clamp-2 sm:truncate sm:max-w-[16rem]`, separate ≥44 px touch-target remove button, `aria-live="polite"`. - **+page.svelte** — chip wired into filter bar; `emptyMessage` extracted as `$derived.by()` with person-wins precedence; `removeDocument` navigation helper strips `?documentId=` and preserves active person filters. - **i18n** — 3 new keys added to de/en/es: `geschichten_filter_document_chip`, `geschichten_filter_remove_document_chip`, `geschichten_empty_for_document`. ## Test plan - [ ] 34 tests across 3 spec files all pass (`npx vitest run --project=client src/routes/geschichten/...` + `--project=server`) - [ ] `npm run lint` clean - [ ] Verify chip renders when navigating from a document detail page with `?documentId=` in the URL - [ ] Verify removing the chip clears `documentId` but preserves active `personId` params - [ ] Verify empty-state shows "Noch keine Geschichten zu diesem Brief" when no results and document filter is active 🤖 Generated with [Claude Code](https://claude.ai/claude-code)
marcel added 7 commits 2026-06-12 10:07:04 +02:00
Hand-curated, year-banded vertical timeline weaving derived person
life-events, curated personal/historical events, and date-placed
letters. Includes proposed sub-issue breakdown for a milestone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace Person birthYear/deathYear integers with birthDate/deathDate +
DatePrecision so known exact birthdays render precisely. Migration,
re-import preservation rule, and bounded blast radius captured; becomes
issue 1 the timeline's derived events depend on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Visual design specs for Milestone #14:
- zeitstrahl-global-concepts.html — A/B/C exploration of the global timeline
- zeitstrahl-final-spec.html — canonical Concept A (global + per-person Lebensweg)
- zeitstrahl-event-editor-spec.html — curator event editor + document quick-action

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(geschichten): integrate DocumentFilterChip into list page
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m21s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 4m48s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 26s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m9s
1de10986c3
Add DocumentFilterChip to the filter bar, extract emptyMessage as $derived.by() with person-wins precedence, and add removeDocument navigation helper. Update tests: add document-filter chip and empty-state-precedence suites, fix person-chip click test to use native element.click() + vi.waitFor() for reliable Svelte 5 onclick triggering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel force-pushed feat/issue-803-geschichten-document-filter-chip from b2b3eb0b1c to 1de10986c3 2026-06-12 10:07:04 +02:00 Compare
marcel added 1 commit 2026-06-12 10:32:11 +02:00
fix(geschichten): correct three bugs found in PR review
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m36s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 5m21s
CI / fail2ban Regex (pull_request) Successful in 52s
CI / Semgrep Security Scan (pull_request) Successful in 26s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m5s
8eb4a0ffde
- Pass validated documentId (not raw) to /api/geschichten — Spring backend
  declares @RequestParam UUID documentId; invalid strings cause HTTP 400
- rebuildUrl no longer deletes documentId unconditionally; clearAll and
  removeDocument handle their own documentId deletion explicitly, so
  removePerson/addPerson now preserve an active document filter
- Use ?? instead of || when falling back from doc.title to originalFilename,
  preserving an intentionally-empty title rather than overwriting it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel merged commit 8eb4a0ffde into feat/issue-750-lesereisen-data-model 2026-06-12 10:45:50 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#811