# 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.svelte` is 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 content
- `document/DocumentThumbnail.svelte` — inline document references
- `shared/discussion/` — comment thread below published stories

## Backend counterpart

`backend/src/main/java/org/raddatz/familienarchiv/geschichte/README.md`
