The dropdown and editor typed /api/persons list items as the full Person
entity. The actual wire shape is PersonSummaryDTO, which until the
previous commit had no date fields - so the life-date subtitle rendered
blank in production while fixtures (built from the entity type) kept the
tests green. Retype items as the summary projection and guard the two
personId consumers against the schema-optional id.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mention dropdown renders precise life dates but receives
PersonSummaryDTO items from /api/persons, which only carried the derived
years - the date fields were silently undefined at runtime. Add
birth/death date + precision to the projection and all four native
queries (searchWithDocumentCount's GROUP BY already listed the columns).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
birthDatePrecision/deathDatePrecision are @Schema REQUIRED, so the
generated Person type makes them non-optional — fixtures that were
type-clean before the regen get UNKNOWN defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New PersonLifeDateField (German date input + hidden ISO + DAY/MONTH/YEAR
precision select, min-h-44px, sm: side-by-side) used for birth and death
in both forms. Legacy APPROX precision seeds the select as YEAR so an
untouched save never claims DAY. Server actions send date+precision
pairs or omit both; obsolete year i18n keys removed, 9 form keys added.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cards compose aria-hidden * / † glyphs in markup so screen readers only
announce the dates; PersonSummaryDTO list card stays year-shaped by
design (ADR-039). MentionDropdown subtitle wraps instead of truncating
so DAY-precision ranges fit at 320px.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New formatLifeDate single-date helper carries no glyph so cards can wrap
* / † in aria-hidden spans. Missing precision falls back to YEAR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Person gains birthDate/deathDate + required precision enums;
PersonSummaryDTO, PersonNodeDTO, and RelationshipDTO keep derived
integer years. familyForest/buildLayout tests still pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-check aborts on corrupt year data, backfills YYYY-01-01/YEAR,
adds five named CHECK constraints, drops birth_year/death_year.
Staged-Flyway Testcontainers test covers pre-check aborts, backfill
shapes, and post-migration schema.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend enum, frontend ErrorCode mirror, getErrorMessage cases, and
error message i18n keys (de/en/es) incl. the mixed-precision workaround
hint in error_birth_after_death.
Co-Authored-By: Claude Fable 5 <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>
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>
lesereisen-reader-spec.html — Issue #752
LR-0 type selector on /geschichten/new
LR-1 REISE badge on the list
LR-2 Journey reader (ordered cards, interlude asides, no position numbers)
lesereisen-editor-spec.html — Issue #753
LE-1 empty JourneyEditor layout
LE-2 editor with mixed items (documents + interludes, drag handles)
LE-3 inline note-editing state
LE-4 mobile layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 1: Create standalone FastAPI service scaffold with models, test framework,
and documentation. Includes ParseRequest, ParseResponse Pydantic models matching
OllamaExtraction contract, plus three passing tests validating model validation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Assert that when the same person id is returned by two different token
fetches, the person appears exactly once in the result -- pinning
fetchPool's putIfAbsent dedup so a future refactor can't silently
double-classify a candidate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AC#4 (maiden alias -> direct) and AC#5 (alias first name -> fetchable +
classifiable) were each split across PersonRepositoryTest (the fetch) and
PersonServiceTest (the classifier with stubs) -- nothing walked
searchByName -> resolveByName end-to-end on real Postgres. Add two tests
in the existing @DataJpaTest slice that build a real PersonService over
the autowired repositories, persist a person with a MAIDEN_NAME alias and
one with an alias firstName, and assert both classify as direct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Addresses @Markus review: tags fetched by findByNameContaining live outside
any transaction; Hibernate's dirty-check never fires on them. The comment
removes the ambiguity for cold readers.
Co-Authored-By: Claude Sonnet 4.6 <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>