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>
geschichte (frontend)
UI for family stories (Geschichte) and reading journeys (Lesereise): the rich-text editor, story/journey readers, type badge, and list rows.
What this domain owns
Components: GeschichteEditor.svelte, GeschichteSidebar.svelte, StoryDocumentPanel.svelte, JourneyEditor.svelte, JourneyItemRow.svelte, JourneyAddBar.svelte, GeschichtenCard.svelte, GeschichteListRow.svelte, StoryReader.svelte, JourneyReader.svelte, JourneyItemCard.svelte, JourneyInterlude.svelte.
Utilities: utils.ts.
What this domain does NOT own
- Comment/discussion UI — shared via
shared/discussion/(same component used for document comments) - Person display —
person/PersonChip.svelteis used inside story content (cross-domain import) - Document display — document references in stories use components from
document/
Key components
| Component | Used in | Notes |
|---|---|---|
GeschichteEditor.svelte |
/geschichten/new, /geschichten/[id]/edit |
Rich-text editor (TipTap) for STORY type; delegates sidebar to GeschichteSidebar |
GeschichteSidebar.svelte |
GeschichteEditor, JourneyEditor |
Status badge + PersonMultiSelect sidebar; <details> mobile collapsibles with 44px touch targets |
StoryDocumentPanel.svelte |
GeschichteSidebar (STORY edit only) |
Sidebar document list for stories: picker add (POST), optimistic remove (DELETE), deleted-doc placeholders |
JourneyEditor.svelte |
/geschichten/[id]/edit (JOURNEY branch) |
Curator editing surface: title, intro textarea, ordered item list with drag/reorder, add bar, save/publish |
JourneyItemRow.svelte |
JourneyEditor.svelte |
Item row: drag handle, move-up/down, note textarea (PATCH on blur), inline remove confirm |
JourneyAddBar.svelte |
JourneyEditor.svelte |
Two add buttons: document picker (DocumentPickerDropdown) and interlude draft form |
GeschichtenCard.svelte |
Person-detail sidebar | Sidebar card showing the 3 most recent stories linked to a person — NOT the list page |
GeschichteListRow.svelte |
/geschichten (list) |
Editorial list row: meta column (avatar, author, date, REISE badge), title + excerpt content column |
StoryReader.svelte |
/geschichten/[id] (STORY branch) |
Renders sanitised rich-text body, persons section, documents section, and author actions |
JourneyReader.svelte |
/geschichten/[id] (JOURNEY branch) |
Renders intro paragraph, ordered items list, empty-state; delegates to ItemCard/Interlude |
JourneyItemCard.svelte |
JourneyReader.svelte |
Card per document item: title, meta line (date · von X an Y), "Brief öffnen →" link, mint-border note |
JourneyInterlude.svelte |
JourneyReader.svelte |
Left-accent interlude box between letters (mode-aware tokens); aria-label="Kuratorennotiz" |
utils.ts
formatAuthorName(author) — joins firstName + lastName, falls back to the localized person_unknown key (for list/summary shapes; email is not exposed).
formatAuthorDisplayName(author) — returns displayName, localizing the server's [Unbekannt] fallback (for detail AuthorView shape).
formatDocumentMetaLine(doc) — "12.07.1938 · von Franz an Emma"; shared by JourneyItemCard, JourneyItemRow, and the story doc-reference cards.
formatPublishedAt(publishedAt, style) — wraps formatDate with null check; style is 'short' (list) or 'long' (detail).
Public list is PUBLISHED-only
GET /api/geschichten constrains status=PUBLISHED, so DRAFT journeys never appear in the list.
The REISE badge is only ever seen for published journeys.
Empty-state and draft-preview paths are reachable ONLY via the detail route (/geschichten/[id]), not the list.
Wire empty-state E2E tests through the detail route, not by expecting a draft journey in the list.
TypeSelector route component
TypeSelector.svelte lives in src/routes/geschichten/new/ (single-use route UI).
It is NOT in $lib/geschichte/ — route-specific, not reused elsewhere.
StoryCreate.svelte (also in new/) wraps GeschichteEditor so tree-shaking excludes TipTap from the JOURNEY placeholder path.
Audience note
The /geschichten route primarily serves readers (younger family members on mobile). Cards must have ≥ 44 px touch targets. Status must not rely on color alone. JourneyReader mobile layout is Critical; TypeSelector is Minor.
Cross-domain imports
person/PersonChip.svelte— inline person references in story contentdocument/DocumentThumbnail.svelte— inline document referencesshared/discussion/— comment thread below published stories
Backend counterpart
backend/src/main/java/org/raddatz/familienarchiv/geschichte/README.md