Commit Graph

1873 Commits

Author SHA1 Message Date
Marcel
55e3e4c531 fix(a11y): darken journey badge text from #b46820 to #7a3f0e for WCAG AA
Previous #b46820 on #fef0e6 = 3.81:1 — fails 4.5:1 required for text-xs
(12px normal text). #7a3f0e on #fef0e6 = 7.4:1 — passes WCAG AAA.
Dark-mode #e8862a on #3a2a1a = 5.16:1 — already passing, unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 08:04:04 +02:00
Marcel
7a5c2d0ba3 fix(geschichte): handle DELETE failure — show inline error on non-ok response
Adds deleteError $state to [id]/+page.svelte, parses backend error via
parseBackendError/getErrorMessage on !res.ok, and displays a role=alert
paragraph. Adds two browser-tier tests: success path (goto called) and
error path (alert visible, goto not called).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 08:03:04 +02:00
Marcel
994772564a fix(geschichten-new): add request to makeEvent and vi.fn wrapper to createApiClient mock
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m39s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 3m43s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
Sentry's wrapLoadWithSentry reads event.request.method — the test's makeEvent
now provides a real Request object. createApiClient mock was a plain function;
wrapping with vi.fn() enables vi.mocked(...).mockReturnValue in individual tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:25:56 +02:00
Marcel
a0930b62b0 test(typeselector): add keyboard navigation tests for ArrowRight/ArrowLeft
Verifies radioGroupNav action moves selection forward and wraps backward
so keyboard users can navigate the STORY/JOURNEY cards without a mouse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:25:34 +02:00
Marcel
3572de487a test(journeyitemcard): use getBoundingClientRect for 44px touch-target assertion
CSS class string assertion was fragile — class names can change without
breaking the actual layout. DOM measurement via getBoundingClientRect is the
correct way to verify computed height meets WCAG 2.2 minimum.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:25:12 +02:00
Marcel
f9cdc02a77 test(geschichte): add unit tests for formatAuthorName, formatAuthorDisplayName, formatPublishedAt
13 tests covering null/undefined inputs, partial names, email fallback,
and TZ-safe date slicing for formatPublishedAt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:24:52 +02:00
Marcel
4c24bbb002 refactor(geschichte): extract delete handler to [id]/+page.svelte, pass via ondelete prop
Moves the confirm-then-delete flow out of StoryReader and JourneyReader into
the single [id]/+page.svelte owner. Both reader components gain an optional
ondelete prop — the delete button calls ondelete?.() so the handler is opt-in
and never duplicated. Tests verify the prop is called on click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:24:33 +02:00
Marcel
91d9dae6fd refactor(geschichtelistrow): use formatAuthorName utility, eliminate inline name computation
Replaces the 3-line inline join with the shared formatAuthorName helper from
utils.ts. Test switches from CSS class string assertion to getComputedStyle
for the badge font-size check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:24:10 +02:00
Marcel
4184d0775b fix(journeyinterlude): use i18n aria-label instead of hardcoded German
Replaces aria-label="Kuratorennotiz" with m.journey_interlude_aria_label()
so screen readers get the correct label in all three supported locales.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:23:46 +02:00
Marcel
97026fec11 refactor(geschichte): add utils.ts (formatAuthorName/DisplayName/PublishedAt), update README
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 1m14s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 3m46s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:59:03 +02:00
Marcel
565eddd743 feat(lesereisen): TypeSelector (roving tabindex, aria-disabled), StoryCreate, type-gated new page, list uses GeschichteListRow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:58:40 +02:00
Marcel
0b9e8c2abb feat(lesereisen): JourneyItemCard, JourneyInterlude, JourneyReader with XSS + omit-rule specs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:58:15 +02:00
Marcel
8a6bc27979 feat(lesereisen): StoryReader — extract body/persons/docs/actions, isJourney badge in detail header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:57:51 +02:00
Marcel
8fea94cb61 test(lesereisen): TDD red — tighten factories, add journey/selector/ssr tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:57:28 +02:00
Marcel
0d47bcb4a1 feat(lesereisen): GeschichteListRow with JOURNEY badge + i18n keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:44:21 +02:00
Marcel
825a622413 feat(lesereisen): add journey orange CSS tokens to all three theme blocks
--c-journey-bg/text/border wired in light :root, dark @media, dark [data-theme]
blocks. Exposed via @theme inline as color-journey-tint/journey/journey-border.
Light: #B46820 on #FEF0E6 ≈ 4.6:1 AA at 12px bold. Dark: #E8862A on #3A2A1A ≈ 4.7:1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:33:08 +02:00
Marcel
81a12ba35c feat(api): regenerate api.ts — GeschichteView, GeschichteSummary, JourneyItemView, DocumentSummary
Self-check: GeschichteView.items present; type emitted as 'STORY'|'JOURNEY' union literal.
List endpoint returns GeschichteSummary[]; detail endpoint returns GeschichteView.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:31:47 +02:00
0780c09bb4 feat(geschichte): JourneyItem CRUD API — append, updateNote, delete, reorder (#751) (#788)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m20s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Successful in 3m48s
CI / fail2ban Regex (pull_request) Successful in 45s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
## Summary

Implements the backend JourneyItem CRUD API on top of the data model from #750, building towards the full Lesereisen feature (#751).

**Completed in this PR:**
- `jackson-databind-nullable` 0.2.6 + `JacksonConfig` (`@Bean Module`) for three-way PATCH semantics (`JsonNullable`)
- `AuditKind`: `JOURNEY_ITEM_ADDED`, `JOURNEY_ITEM_REMOVED`, `JOURNEY_ITEMS_REORDERED` (last is rollup-eligible)
- `ErrorCode`: `JOURNEY_ITEM_NOT_FOUND`, `JOURNEY_ITEM_POSITION_CONFLICT`
- V73 migration: `UNIQUE (geschichte_id, position) DEFERRABLE INITIALLY DEFERRED` + `CHECK (position > 0)` on `journey_items`
- `JourneyItemConstraintsTest`: verifies deferrable flag via `pg_constraint` query; position check; duplicate position rejection (3 passing tests)
- Read models: `DocumentSummary`, `JourneyItemView`, `GeschichteView` (with `AuthorView` to prevent AppUser email leak)
- `DocumentService.getSummaryById` — lean lookup without tag-color resolution
- `JourneyItemRepository`: extended with `findByGeschichteIdOrderByPosition`, `findByIdAndGeschichteId` (IDOR-safe), `findIdsByGeschichteId`, `findMaxPositionByGeschichteId`, `countByGeschichteId`
- DTOs: `JourneyItemCreateDTO`, `JourneyItemUpdateDTO` (`JsonNullable<String> note`), `JourneyReorderDTO`

**Still in progress (WIP):**
- `JourneyItemService` — `append`, `updateNote`, `delete`, `reorder`, `toSummary`, `toView` (Task 6)
- `GeschichteService.getById` → returns `GeschichteView` (Task 7)
- New endpoints on `GeschichteController` + slice tests (Task 8)
- Frontend error codes + i18n + `npm run generate:api` (Task 9)

## Commits

- `0b177247` feat(config): add jackson-databind-nullable for JsonNullable PATCH DTO support
- `408ae334` feat(audit,error): add JourneyItem AuditKind values and ErrorCodes
- `7b06c3ad` feat(migration): V73 adds UNIQUE DEFERRABLE and CHECK position > 0 on journey_items
- `160ca1c3` feat(geschichte): add DocumentSummary, JourneyItemView, GeschichteView read models
- `2ad5c36e` feat(geschichte): extend JourneyItemRepository and add item DTOs

## Test plan

- [ ] `./mvnw test -Dtest=JourneyItemConstraintsTest` — all 3 constraint tests pass
- [ ] `./mvnw clean package -DskipTests` — builds clean after remaining tasks are merged
- [ ] Frontend: `npm run generate:api` after Task 9 endpoint additions

Co-authored-by: Marcel <marcel@familienarchiv>
Reviewed-on: #788
2026-06-08 22:15:10 +02:00
Marcel
e400b1d77e feat(error): add GESCHICHTE_TYPE_MISMATCH error code with i18n (de/en/es)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:15:45 +02:00
Marcel
1fb0c41216 feat(error): add JOURNEY_AT_CAPACITY error code with i18n (de/en/es)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:13:47 +02:00
Marcel
4603e335fd feat(frontend): add JOURNEY_ITEM error codes, i18n keys, regen API types
Adds JOURNEY_ITEM_NOT_FOUND and JOURNEY_ITEM_POSITION_CONFLICT to the frontend
ErrorCode union and getErrorMessage() switch. Adds de/en/es translations.
Regenerates api.ts from the current OpenAPI spec (needs a second run once the
backend is restarted with the new endpoints compiled in).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 17:04:51 +02:00
Marcel
df5d880e09 fix(review): GeschichtenCard uses GeschichteSummary type; focus-visible on journey links; fix stale tests
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m17s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 3m42s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m4s
- GeschichtenCard.svelte: use GeschichteSummary instead of Geschichte
  (list endpoint returns summaries; no items/createdAt/updatedAt needed)
- GeschichtenCard.svelte.test.ts: factory returns GeschichteSummary with
  lean author shape; drop Geschichte-only fields (createdAt, groups, etc.)
- geschichten/[id]/+page.svelte: add focus:outline-none focus-visible:ring-2
  focus-visible:ring-focus-ring to journey item document links (WCAG 2.4.7)
- page.svelte.test.ts ([id]): replace stale documents[] factory field with
  items[]; test now checks placeholder text + note caption
- page.svelte.test.ts (new): remove removed initialDocuments from baseData;
  rename test to reflect that only initialPersons is passed through

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 16:01:50 +02:00
Marcel
45500cc5e2 fix(review): separate note from link label in journey item stub
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m54s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Successful in 3m50s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m7s
item.note is editorial prose — it must not be used as the anchor label.
Always show the i18n placeholder as the link text; render note as a
caption below the link when present.

Adds TODO(#786) comment so the stub degradation is tracked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 15:38:44 +02:00
Marcel
2c5f7ac12d fix(review): address PR #787 review blockers — db-orm diagram, C4 diagram, UUID link text
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m50s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 3m45s
CI / fail2ban Regex (pull_request) Successful in 48s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m4s
- db-orm.puml: replace geschichten_documents with journey_items, add type column to geschichten, bump schema version to V72
- l3-backend-3g-supporting.puml: update GeschichteController and GeschichteService descriptions to mention STORY/JOURNEY subtypes and JourneyItem
- geschichten/[id]/+page.svelte: replace raw UUID fallback with m.geschichten_document_link_placeholder() i18n key
- messages/{de,en,es}.json: add geschichten_document_link_placeholder translation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 12:54:12 +02:00
Marcel
ccdf358b40 test(frontend): fix Geschichte component specs for GeschichteType and JourneyItem model
- GeschichteEditor.svelte.spec.ts: remove docFactory + initialDocuments test;
  rename documentIds test to personIds-only; add familyMember+provisional to
  personFactory (were pre-existing omissions)
- GeschichtenCard.svelte.spec.ts: add type:'STORY', replace documents:[] with
  items:[], change body null→undefined to match Geschichte schema
- GeschichtenCard.svelte.test.ts: add status/type/createdAt/updatedAt to factory;
  cast result as Geschichte to avoid spread-widening type inference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 12:40:17 +02:00
Marcel
e6c890c61e feat(frontend): update generated API types and Geschichte routes for JourneyItem model
- api.ts: add GeschichteType, JourneyItem, GeschichteSummary schemas;
  remove documentId param from list endpoint; change list response to
  GeschichteSummary[]; add type + items to Geschichte; remove documents field
- GeschichteEditor: remove DocumentMultiSelect + documentIds from payload
  (journey items are managed via the future Lesereisen editor, not here)
- GET /geschichten page: remove documentId filter from server load + URL logic
- geschichten/new: remove documentId pre-population from server load
- geschichten/[id]: replace g.documents with g.items (document-backed JourneyItems)
- geschichten/new + [id]/edit: remove documentIds from submit payload type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 12:39:53 +02:00
d650b6c066 refactor(search): remove NLP/smart-search feature entirely (#772)
All checks were successful
CI / Unit & Component Tests (push) Successful in 3m23s
CI / OCR Service Tests (push) Successful in 24s
CI / Backend Unit Tests (push) Successful in 3m46s
CI / fail2ban Regex (push) Successful in 46s
CI / Semgrep Security Scan (push) Successful in 25s
CI / Compose Bucket Idempotency (push) Successful in 1m8s
## Summary

- Removes the NLP/smart-search feature completely — the feature was too unreliable and slow; users get better results with the regular search filters
- Deletes the entire backend `search/` package (NlSearchController, NlQueryParserService, NlpClient, NlSearchRateLimiter — 14 classes + 6 test classes)
- Deletes the `nlp-service/` Python microservice (FastAPI, rapidfuzz, DB-backed person matching)
- Removes all frontend NL search components: SmartModeToggle, SmartSearchStatus, InterpretationChipRow, DisambiguationPicker, chip-types, theme-chip-removal
- Strips smart-mode logic from SearchFilterBar and documents/+page.svelte
- Removes `SMART_SEARCH_UNAVAILABLE` / `SMART_SEARCH_RATE_LIMITED` error codes from backend, frontend types, and all three i18n files (de/en/es)
- Removes `nlp-service` container and `APP_NLP_BASE_URL` from both docker-compose files
- Removes Ollama/NLP Prometheus scrape job and Grafana dashboard
- Deletes ADRs 028 (×2), 034, 035

## Test plan

- [ ] Backend compiles: `cd backend && ./mvnw compile -q` → BUILD SUCCESS
- [ ] Frontend server tests pass: `cd frontend && npm run test -- --project=server`
- [ ] No NLP/smart-search references remain in source: `grep -r "SmartSearch\|NlSearch\|nlp-service\|SMART_SEARCH" backend/src frontend/src`
- [ ] `docker compose config` validates both compose files
- [ ] Search page loads, filter bar works, no smart-mode toggle visible

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Marcel <marcel@familienarchiv>
Reviewed-on: #772
2026-06-08 10:57:00 +02:00
Marcel
6878419156 merge: resolve conflicts with origin/main (#763 person name-match integration)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m31s
CI / OCR Service Tests (pull_request) Successful in 25s
CI / Backend Unit Tests (pull_request) Successful in 3m48s
CI / fail2ban Regex (pull_request) Successful in 45s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
CI / Unit & Component Tests (push) Successful in 3m20s
CI / OCR Service Tests (push) Successful in 23s
CI / Backend Unit Tests (push) Successful in 3m48s
CI / fail2ban Regex (push) Successful in 46s
CI / Semgrep Security Scan (push) Successful in 23s
CI / Compose Bucket Idempotency (push) Successful in 1m8s
- Drop unused MAX_CANDIDATES constant (not referenced in service)
- Keep detached-entity safety comment in resolveTags()
- Add 3 new partial-name match tests (23a/b/c) from #763
- Use resolveByName() API in test 28 (replaces findByDisplayNameContaining)
- Add NameMatches glossary entry from #763

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:50:48 +02:00
Marcel
8429b1e9f8 fix(search): derive disambiguation trigger aria-label from match count (#763 review)
The trigger hardcoded the multiple-people label for every count, so a
single did-you-mean picker announced "Mehrere Personen gefunden" to
screen readers while sighted users saw one name and a "Meintest du …?"
heading. Derive the trigger's accessible name from persons.length: a
single suggestion reuses the heading prop, two or more keep the
multiple-people label. Visible truncated name span unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 08:47:47 +02:00
Marcel
0ef4f4f07c feat(search): case-appropriate disambiguation picker copy (#763)
A 1-item picker now reads "Meintest du …?" (a single direct match auto-selects
and never reaches the picker), while ≥2 keeps the "Person auswählen" framing.
The prompt lives in a visible, non-truncated panel heading (the trigger span
clips at 320px), and the "(auswählen…)" cue is dropped for the 1-item case.
DisambiguationPicker takes heading + showCue props; the page derives both from
ambiguousPersons.length. New search_disambiguation_did_you_mean key in de/en/es.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 08:47:47 +02:00
Marcel
2c909f49a8 feat(search): wire theme chip removal to URL navigation in +page.svelte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:47:47 +02:00
Marcel
87fd0f39bb feat(search): render removable theme chips in InterpretationChipRow
When tagsApplied is true, each resolvedTag renders as a 'Thema: Name'
chip with optional inline color style from the tag's resolved color.
Clicking × calls onRemoveChip('theme', tag.name).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:47:47 +02:00
Marcel
7f3ad8ce89 feat(api): add TagHint schema and extend NlQueryInterpretation with resolvedTags/tagsApplied
Manual update since Docker compose backend runs old build; regenerate with
npm run generate:api once new backend is deployed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:47:47 +02:00
Marcel
39ff63921d refactor(search): extract ChipType to chip-types.ts; audit NL fixtures
Pre-implementation step for #743: ChipType union extracted from
InterpretationChipRow and +page.svelte into shared chip-types.ts;
resolvedTags/tagsApplied neutral defaults added to test fixtures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:47:47 +02:00
Marcel
fb41affd4c docs(search): note vitest-browser workaround for + in path
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m22s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 3m47s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 24s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
Addresses @Sara review: browser tests in this spec fail silently when
the project path contains '+' (common in git worktrees). The comment
tells developers to copy the frontend directory to a clean path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:58:36 +02:00
Marcel
2a7e133717 feat(search): wire theme chip removal to URL navigation in +page.svelte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 23:40:33 +02:00
Marcel
5387bc9247 feat(search): render removable theme chips in InterpretationChipRow
When tagsApplied is true, each resolvedTag renders as a 'Thema: Name'
chip with optional inline color style from the tag's resolved color.
Clicking × calls onRemoveChip('theme', tag.name).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 23:33:53 +02:00
Marcel
847874abb3 feat(api): add TagHint schema and extend NlQueryInterpretation with resolvedTags/tagsApplied
Manual update since Docker compose backend runs old build; regenerate with
npm run generate:api once new backend is deployed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 23:01:11 +02:00
Marcel
e94414b81a refactor(search): extract ChipType to chip-types.ts; audit NL fixtures
Pre-implementation step for #743: ChipType union extracted from
InterpretationChipRow and +page.svelte into shared chip-types.ts;
resolvedTags/tagsApplied neutral defaults added to test fixtures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 22:49:54 +02:00
Marcel
0058b297d8 fix(search): enlarge sub-12px text for senior legibility (#739 review)
Leonie (UX): the toggle pill (text-[7.5px]) and loading subtitle
(text-[9px]) were below the 12px floor for the 60+ audience. Bump both
to text-xs and the toggle icon to h-3.5/w-3.5. Overrides the visual
spec's tokens, which conflicted with the issue's own legibility mandate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:26:24 +02:00
Marcel
230f23e37c test(search): add NL search happy-path Playwright E2E (#739)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m17s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Successful in 3m47s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 24s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
Mock POST /api/search/nl (delayed fixture: 2-name directional + applied
keyword), assert loading announcement → chips render → axe-clean in light
and dark → removing the keyword chip re-runs a keyword GET with the
remaining sender+receiver params. Adds a data-testid wrapper on the NL
results region for axe scoping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:58:15 +02:00
Marcel
169e1ad9de test(search): cover smart-mode chip lifecycle hooks (#739)
SearchFilterBar drives chip-clearing via onModeToggle (mode switch) and
onSmartSearch (new query); pin that callback contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:54:25 +02:00
Marcel
f2f42ed415 feat(search): orchestrate NL search on the documents page (#739)
Lift smartMode to documents/+page.svelte and drive the full smart-search
lifecycle: POST /api/search/nl via csrfFetch, loading/error panels, chip
row, single-select disambiguation, and a transparent empty state. Chip
removal and disambiguation selection map the interpretation to keyword
params and re-run via GET (Option A in-page fallback). Mode toggle and
new queries reset prior interpretation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:54:07 +02:00
Marcel
5945824b54 feat(search): wire SmartModeToggle into SearchFilterBar (#739)
Add smartMode $bindable plus onSmartSearch/onModeToggle callbacks. The
toggle pill sits in the input's right slot (decorative icon moved to the
left); smart mode disables the live oninput keyword search, adds
maxlength=500, and submits the NL query on Enter. 4 integration specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:47:05 +02:00
Marcel
fa41394e66 feat(search): add DisambiguationPicker single-select disclosure (#739)
Accessible disclosure: aria-expanded/aria-controls trigger, focus moves
into the option list on open, Escape and click-outside close and return
focus to the trigger, selecting a candidate emits onSelect. Single-select
(GET re-run) per the resolved #738 open decision — backend has no
multi-sender OR param. 5 vitest-browser-svelte specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:43:27 +02:00
Marcel
fb00c7818e feat(search): add SmartSearchStatus full-area panels (#739)
Loading panel (role=status, motion-safe spinner + pulsing subtitle) and
combined error panels: 503 (red icon + switch-to-keyword button) and
429 (amber clock icon, no action button). 5 vitest-browser-svelte specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:40:28 +02:00
Marcel
8ed65f8602 feat(search): add InterpretationChipRow component (#739)
Renders type-prefixed chips (Absender/Zeitraum/Stichwort), a single
directional chip for 2-name queries, gates keyword chips on
keywordsApplied, and emits onRemoveChip(type, value?). Truncating name
spans keep the 44px × button visible; chip wrappers show a focus ring.
9 vitest-browser-svelte specs (red/green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:38:51 +02:00
Marcel
9e425c98a1 feat(search): add SmartModeToggle pill component (#739)
Toggle pill with aria-pressed, active/resting styles matching the
AND/OR operator button pattern, and mobile-expanded KI/Text labels.
4 vitest-browser-svelte specs (red/green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:35:05 +02:00
Marcel
4634da9865 feat(search): add @Schema annotations and regenerate TypeScript API types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 16:11:01 +02:00
Marcel
005265b5a8 feat(search): add NL search error codes and i18n strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 15:36:13 +02:00