diff --git a/frontend/src/lib/geschichte/README.md b/frontend/src/lib/geschichte/README.md index 74baea7b..49024cfc 100644 --- a/frontend/src/lib/geschichte/README.md +++ b/frontend/src/lib/geschichte/README.md @@ -1,10 +1,11 @@ # geschichte (frontend) -UI for family stories: the rich-text editor, story cards, and story list view. +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`, `GeschichtenCard.svelte`. +Components: `GeschichteEditor.svelte`, `GeschichtenCard.svelte`, `GeschichteListRow.svelte`, `StoryReader.svelte`, `JourneyReader.svelte`, `JourneyItemCard.svelte`, `JourneyInterlude.svelte`. +Utilities: `utils.ts`. ## What this domain does NOT own @@ -14,14 +15,38 @@ Components: `GeschichteEditor.svelte`, `GeschichtenCard.svelte`. ## Key components -| Component | Used in | Notes | -| ------------------------- | -------------------------------------------- | ------------------------------------------------------------------ | -| `GeschichteEditor.svelte` | `/geschichten/new`, `/geschichten/[id]/edit` | Rich-text editor with person/document @-mentions and inline embeds | -| `GeschichtenCard.svelte` | `/geschichten` (list), dashboard | Story preview card with cover image and publish status | +| Component | Used in | Notes | +| -------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `GeschichteEditor.svelte` | `/geschichten/new`, `/geschichten/[id]/edit` | Rich-text editor with person/document @-mentions and inline embeds | +| `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) | Row component for the list; shows JOURNEY type badge (`text-xs` orange pill) | +| `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` | Whole-card `` for a document item; dated/undated aria-label, ✎ annotation glyph | +| `JourneyInterlude.svelte` | `JourneyReader.svelte` | Typographic aside between letters; ❦ glyph, `aria-label="Kuratorennotiz"` | + +## utils.ts + +`formatAuthorName(author)` — joins `firstName + lastName`, falls back to `email` (for list/summary shapes). +`formatAuthorDisplayName(author)` — returns `displayName` (for detail `AuthorView` shape). +`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. +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 diff --git a/frontend/src/lib/geschichte/utils.ts b/frontend/src/lib/geschichte/utils.ts new file mode 100644 index 00000000..824ad996 --- /dev/null +++ b/frontend/src/lib/geschichte/utils.ts @@ -0,0 +1,28 @@ +import { formatDate } from '$lib/shared/utils/date'; + +type AuthorSummary = { firstName?: string; lastName?: string; email: string }; +type AuthorView = { displayName: string }; + +/** Format an author name from a list summary (firstName + lastName, falling back to email). */ +export function formatAuthorName(author: AuthorSummary | null | undefined): string { + if (!author) return ''; + const full = [author.firstName, author.lastName].filter(Boolean).join(' ').trim(); + return full || author.email || ''; +} + +/** Format an author displayName from a detail view. */ +export function formatAuthorDisplayName(author: AuthorView | null | undefined): string { + return author?.displayName ?? ''; +} + +/** + * Format a publishedAt ISO string to a localised date string. + * Returns null when publishedAt is absent. + */ +export function formatPublishedAt( + publishedAt: string | null | undefined, + style: 'short' | 'long' = 'short' +): string | null { + if (!publishedAt) return null; + return formatDate(publishedAt.slice(0, 10), style); +}