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>
GLOSSARY entry for NameMatches (direct vs partial name-match strength and how
the search layer maps it); person/README adds resolveByName to the public
surface. No ADR — the matching rule is localized and justified inline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
resolveNames now delegates to PersonService.resolveByName and maps by match
strength: 1 direct → resolved (auto-select), ≥2 direct → ambiguous, 0 direct
with partials → ambiguous suggestions, 0 candidates → folded into full-text.
A single direct match no longer forces the picker when looser substring hits
coexist. The MAX_CANDIDATES cap moved into PersonService (after classification);
the MAX_NAME_LENGTH guard, resolved-cap overflow, and sender/receiver mapping
are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Token-set containment over all of a person's name components (firstName,
lastName, alias, each PersonNameAlias first+last, title) decides direct vs
partial. Orchestrates tokenize → cap(8) → fetch pool → classify → cap(10)
after classification, with an empty-token guard and a PII-free debug log of
the outcome bucket. MAX_TOKENS is a DoS control; the after-classify cap keeps a
direct match that sorts past position 10 among partials. Read-only transaction
keeps lazy nameAliases reachable during classification (ADR-022).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The direct-match classifier accepts alias firstName tokens, so the fetch must
surface candidates matchable only via an alias first name. Add a.firstName to
the searchByName LIKE clause (reuses the bound :query — injection-proof). The
person_name_aliases.first_name column already exists; no migration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lowercase, split on whitespace/hyphen/apostrophe, drop empties. Applied
symmetrically to query and candidate name components so "Anna-Maria" and
"Anna Maria" tokenize alike. Foundation for resolveByName direct matching.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Verifies the recursive CTE in findDescendantIdsByName expands a parent tag
to include all child IDs, and that findByNameContainingIgnoreCase matches
both parent and child names when the fragment appears in both.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers multi-tag match, no-match FTS fallback, mixed resolution, personRole
bypass, cap at 10, short-keyword skip, dedup, rawQuery suppression when all
keywords resolve, flag independence, colour propagation via resolveEffectiveColors,
and colour=null when depth constraint prevents resolution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keywords that substring-match the tag taxonomy become OR-union tag filters;
non-matching keywords stay as FTS text. Resolved tags surface in the
NlQueryInterpretation as TagHint objects with effective colours. The
rawQuery fallback is now guarded by hadStructuredMatch to prevent
double-apply when all keywords resolve.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Positional record fields added; all 3 construction sites updated with neutral
defaults; NlQueryParserService wired for TagService (4th constructor arg);
NlQueryParserServiceTest and NlSearchControllerTest synced.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Markus (architect): document SearchFilterBar + the search/ components
(SmartModeToggle, InterpretationChipRow, SmartSearchStatus,
DisambiguationPicker) and the POST /api/search/nl relation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Add the smart-search sub-component directory to the frontend Project
Structure tree (merge blocker per #739).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
Toggle labels, loading panel, error panels (503/429), empty-state
retry, chip type-prefixes + remove label, and disambiguation strings
for the smart search UI (#739). Formal Sie form per project standard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both the first-time model pull runbook (from this branch) and the model
upgrade procedure (from main) belong in DEPLOYMENT.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the SmartModeToggle pill (inside the search input, Google AI Mode
style), InterpretationChipRow anatomy, DisambiguationPicker, and all
status/error/empty states as full-result-area panels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- error_smart_search_unavailable/rate_limited now use "Sie" (formal) to
match the tone of all existing German error messages
- Replace inline FQNs in DocumentService.buildPersonSpec with proper
JoinType + Predicate imports
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch to wiremock-jetty12 artifact and force ee10 Jetty deps to 12.1.8
to resolve compatibility with Spring Boot 4's Jetty 12.1.8 core.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses Elicit's and Sara's review concerns on PR #749:
- Expand §6 ollama_models section into a full model upgrade runbook (step-by-step
docker volume rm + recreate, including production volume name prefix)
- Add re-deploy idempotency note to §3.4 (init container exits quickly when model
already present on the volume)
- Add NL search smoke test to §3.4 (curl command distinguishing 200 from 503
NL_SEARCH_UNAVAILABLE)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>