Typauswahl bei /geschichten/new, Journey-Badge auf der Übersichtsliste und die neue geordnete Leseansicht auf /geschichten/[id] wenn type === 'JOURNEY'. Bestehende Story-Ansichten bleiben unverändert.
Alle angemeldeten Familienmitglieder können Lesereisen entdecken und in Briefsequenzen mit Kuratoren-Notizen eintauchen. BLOG_WRITERs sehen zusätzlich Bearbeiten/Löschen-Aktionen.
Eine Lesereise ist eine Geschichte mit type === 'JOURNEY'. Ihr Kerninhalt ist eine geordnete Sequenz von Briefen (JourneyItems mit document_id) und Zwischentexten (JourneyItems ohne document_id). Das optionale Feld body dient als Einleitung/Preface.
Diese Spec deckt drei Änderungen ab: (1) die Typauswahl auf /geschichten/new als vorgelagerter Schritt, (2) das „REISE"-Badge in der Übersichtsliste, und (3) die neue Journey-Leseansicht auf der Detailseite, die den bestehenden Prosa-Body durch eine nummerierte Briefliste ersetzt.
Dokument-Items zeigen Titel, Datum, Sender→Empfänger und einen Link zum Brief. Optionale Kuratoren-Notizen erscheinen als Annotation mit Mint-Linker-Rand unter dem Briefeintrag. Interlude-Items (kein Dokument) erscheinen als eingerückte Absätze mit orangenem linken Rand — klar vom Dokumenttyp unterscheidbar, aber harmonisch im Lesefluss.
Neuer vorgelagerter Schritt beim Erstellen einer Geschichte. Zwei Karten zur Auswahl: „Geschichte" (Prosa) und „Lesereise" (Briefsequenz). Die ausgewählte Karte wird hervorgehoben. Erst nach Auswahl wird der „Weiter"-Button aktiv. Auswahl bleibt im URL-Param erhalten (?type=JOURNEY).
Varianten: Keine Auswahl (Weiter-Button inaktiv) · Lesereise gewählt (hier gezeigt) · Geschichte gewählt
| Element | Wert | Hinweise |
|---|---|---|
| Layout | ||
| Selector area | flex flex-1 items-center justify-center bg-canvas px-6 py-10 | zentriert, füllt restliche Höhe |
| Frage | font-serif text-sm text-ink-2 text-center mb-4 | |
| Karten-Grid | flex gap-4 | 2 gleich breite Karten; auf Mobile flex-col |
| Type-Karte | ||
| Karte (inaktiv) | border border-line rounded-md p-4 bg-white cursor-pointer hover:border-primary hover:bg-surface | focus-visible:ring-2 focus-visible:ring-primary |
| Karte (ausgewählt) | border-2 border-orange-500 bg-orange-50 shadow-sm | aria-pressed="true"; kein Tailwind-Kürzel — nutze CSS-var(--orange) |
| Check-Kreis | w-5 h-5 rounded-full bg-orange-500 flex items-center justify-center self-end mt-2 | nur sichtbar wenn ausgewählt |
| Kartentitel | font-serif text-sm text-ink | |
| Kartenbeschreibung | text-xs text-ink-3 leading-relaxed mt-1 | |
| Navigation | ||
| Weiter-Button | rounded border border-primary bg-primary text-white px-4 py-2 text-sm font-medium disabled:opacity-40 | disabled wenn keine Karte ausgewählt |
| URL-Param | ?type=STORY | ?type=JOURNEY | per goto() nach Klick auf Weiter; lesefreundlich bookmarkbar |
| Mobile | flex-col Karten; volle Breite | kein Scrollbedarf auf 320px |
Die Übersichtsliste erhält ein kleines „REISE"-Badge in der Metaspalte einer Journey-Zeile — unterhalb von Datum und Personenchip. Zeilen mit type === 'STORY' bleiben unverändert. Das Badge ist nicht klickbar, dient als reine visuelle Unterscheidung.
Varianten: Mischte Liste (hier gezeigt) · Nur-Journey-Filter · Nur-Story-Ansicht (unverändert)
| Element | Wert | Hinweise |
|---|---|---|
| Badge | ||
| Journey badge | inline-flex items-center px-1.5 py-px rounded-sm text-[10px] font-bold uppercase tracking-wide bg-orange-50 text-orange-700 border border-orange-200 | nur wenn type === 'JOURNEY' |
| Position Desktop | unterhalb Datum-Text und Personenchip in der Metaspalte (g-meta) | kein extra Abstand nötig — gap-1 der Flex-Spalte reicht |
| Position Mobile | inline flex items-center gap-1.5 neben Titel | Titel + Badge in einem flex-Wrapper; badge shrink-0 |
| aria-label | aria-label="Lesereise" | Badge ist span, kein interaktives Element |
| Bedingte Logik | ||
| Svelte guard | {#if geschichte.type === 'JOURNEY'}<span …>REISE</span>{/if} | kein Badge für STORY |
Wenn type === 'JOURNEY' ersetzt die geordnete Briefliste den Prosa-Body. Optional zeigt ein Einleitungsabsatz (body) vor den Items. Jedes Item ist entweder ein Briefeintrag (Kartentitel, Datum, Link) oder ein Interlude-Absatz (orangener linker Rand, kursiv). Die Reihenfolge ergibt sich von oben nach unten — keine Nummern. Briefeinträge können eine optionale Kuratoren-Annotation unter dem Link zeigen.
Varianten: Leserin ohne Schreibrecht · BLOG_WRITER (Bearbeiten/Löschen sichtbar — hier gezeigt) · Mobile
| Element | Wert | Hinweise |
|---|---|---|
| Seitenstruktur | ||
| Bedingte Logik | {#if geschichte.type === 'JOURNEY'} JourneyReader {:else} StoryReader {/if} | in +page.svelte von /geschichten/[id] |
| Artikel-Container | max-w-3xl mx-auto px-4 py-8 | gleich wie StoryReader |
| Journey-Badge | inline-flex px-2 py-px rounded-sm text-[10px] font-bold uppercase tracking-widest bg-orange-50 text-orange-700 border border-orange-200 mb-2 | über dem Titel; nicht für STORY |
| Titel | font-serif text-3xl text-ink leading-tight mb-4 | gleich wie Story |
| Metabar | flex items-center gap-3 pb-4 border-b border-subtle mb-4 | gleich wie Story |
| Bearbeiten/Löschen | nur BLOG_WRITE; auf Mobile im ··· BottomSheet | gleich wie Story |
| Intro-Absatz | ||
| Intro (body) | font-serif text-sm text-ink-2 italic leading-relaxed mb-6 pb-4 border-b border-dashed border-subtle | nur rendern wenn body nicht leer; kein HTML-Rendering — plaintext |
| Dokument-Item | ||
| Item-Zeile | mb-3 | kein flex nötig — Karte ist full-width |
| Dokumentkarte | bg-white border border-line rounded-sm p-3 | |
| Brieftitel | font-serif text-sm text-ink leading-snug mb-0.5 | document.title |
| Briefmeta | text-xs text-ink-3 mb-2 | formatDate(document.documentDate) · "von X an Y" |
| Brief öffnen Link | inline-flex items-center gap-1 text-xs font-semibold text-ink hover:text-primary | href="/documents/{item.document.id}" |
| Kuratoren-Annotation | ||
| Annotation | mt-3 pl-3 border-l-2 border-mint bg-surface rounded-r-sm py-1.5 pr-2 | nur rendern wenn item.note vorhanden |
| Annotations-Text | text-xs italic text-ink-2 leading-relaxed | |
| Interlude-Item | ||
| Interlude-Block | pl-3 border-l-2 border-orange-400 bg-orange-50 rounded-r-sm py-2 pr-3 my-4 | item.document === null |
| Interlude-Text | text-xs italic text-ink leading-relaxed | item.note; plaintext |
| Mobile | ||
| ··· Menü | ml-auto text-ink-3; öffnet BottomSheet mit Bearbeiten + Löschen | BLOG_WRITE; gleich wie Story |
| Touch Target (Brief öffnen) | min-h-[44px] durch padding auf der Karte | WCAG 2.2 AA |
| View | Route | Änderung |
|---|---|---|
| Neue Geschichte | /geschichten/new | Neuer Typauswahl-Schritt als first render; setzt ?type=STORY|JOURNEY |
| Geschichten-Liste | /geschichten | Journey-Badge in GeschichtenCard wenn type === 'JOURNEY' |
| Geschichte-Detail | /geschichten/[id] | Bedingte Verzweigung: JourneyReader | StoryReader |
JourneyReader.svelte — rendert Intro + Items-Liste; Props: geschichte: GeschichteDetailJourneyItemCard.svelte — ein Dokument-Item mit optionaler Annotation; Props: item: JourneyItem, position: numberJourneyInterlude.svelte — ein reiner Text-Interlude; Props: note: stringGeschichteType: 'STORY' | 'JOURNEY'JourneyItem: { id: UUID, position: number, document: DocumentSummary | null, note: string | null }Geschichte.items — geordnete Liste (nach position ASC); für STORY leerGeschichte.body — für JOURNEY der optionale Einleitungstext (plaintext, kein HTML); für STORY der Rich-Text-Body/geschichten/new-Route — kein eigener URL, kein goto(). Zustand let selectedType: GeschichteType | null = null in der Komponente.selectedType !== null ist der „Weiter"-Button aktiviert (disabled={!selectedType}).selectedType === 'JOURNEY' → goto('/geschichten/new?type=JOURNEY') und zeige den Journey-Editor (aus Issue #753); wenn STORY → bestehender GeschichteEditor (unverändert).role="radio" und aria-checked für Accessibility. Keyboard: Arrow-Keys wechseln zwischen den Karten, Space/Enter wählt aus.GeschichtenCard.svelte hinzufügen — keine Änderung an der Listenlogik oder dem API-Aufruf.aria-label="Lesereise" für den Badge-Span.ORDER BY position ASC). Keine client-seitige Sortierung nötig.item.document === null. In diesem Fall: JourneyInterlude-Komponente rendern.body) wird als Plaintext gerendert — nicht als innerHTML. Im Editor wird es als einfaches Textarea gespeichert, kein HTML.currentUser.permissions.includes('BLOG_WRITE') — gleich wie Story.<ol> semantisch für die geordnete Briefliste. Interludes sind <li>-Elemente mit aria-label="Kuratorennotiz".aria-label, z.B. aria-label="Brief vom 12. Juli 1938 öffnen".focus-visible:ring-2 focus-visible:ring-primary auf allen Links.