Geschichten — Writer Journey

Erstellung, Bearbeitung und Veröffentlichung von Geschichten durch BLOG_WRITERs. Routen /geschichten/new und /geschichten/[id]/edit mit geteiltem Layout: Texteditor links, Metadaten-Sidebar rechts.

Final Spec
2026-05-02
@leonievoss
Issue #381
W

Writer Journey

BLOG_WRITERs erstellen und pflegen Geschichten über mehrere Sitzungen, verknüpfen historische Personen und Dokumente und entscheiden selbst über Veröffentlichung.

/geschichten/new · /geschichten/[id]/edit
Konzept

Geschichten werden in dedizierten Vollseiten-Routen verfasst – kein Inline-Edit, kein Modal. Dadurch hat der Writer maximale Konzentration auf den Text und alle Metadaten in einer einzigen Ansicht.

Das Desktop-Layout teilt die Seite im Verhältnis 70/30: links der Fließtext-Editor (Titel + Werkzeugleiste + Body), rechts eine schmale Sidebar für Personen-Verlinkung, Dokumenten-Anhänge und Status. Beide Spalten scrollen unabhängig voneinander.

Die Speicherleiste am unteren Rand ist sticky und verändert ihre Aktionen je nach aktuellem Status der Geschichte: Im Zustand ENTWURF bietet sie "Entwurf speichern" und "Veröffentlichen". Im Zustand VERÖFFENTLICHT bietet sie "Speichern" (live) und "Zurück zu Entwurf" (Retract). Ein "Löschen"-Link erscheint in der Topbar nur dann, wenn die Geschichte bereits existiert (kein Löschen beim Anlegen einer neuen Geschichte).

PersonMultiSelect und ein Dokument-Suche-Typeahead (beide bereits im Projekt als etablierte Patterns vorhanden) übernehmen die Verknüpfung von historischen Personen und Dokumenten. Für den Rich-Text-Body genügt ein minimaler contenteditable-Ansatz mit execCommand für Fett, Kursiv und Absätze – keine externe Bibliothek nötig für den MVP.

Screens

W-1 — /geschichten/new · Neuer Entwurf

US-BLOG-001
Leeres Editor-Layout für eine neue Geschichte. Kein "Löschen"-Link (Geschichte existiert noch nicht). Save-Bar im ENTWURF-Modus mit zwei Aktionen.
Desktop · 1040 px · min-height 560 px · Split 70/30
Desktop — 1040 px
Dokumente Personen Geschichten Gespräche
MR
Neue Geschichte
ENTWURF
B
I
Personen
🔍
Person suchen…
Welche historischen Personen kommen in dieser Geschichte vor?
Dokumente
🔍
Dokument suchen…
Welche Briefe oder Dokumente sind Teil dieser Geschichte?
Status
ENTWURF
Noch nicht öffentlich sichtbar.
Alle Änderungen werden als Entwurf gespeichert.
VALIDIERUNGS-STATE — Titel leer beim Speichern
● Bitte gib einen Titel ein.

impl-ref · W-1

ElementWert / KlassenAnmerkung
Layout
Seiten-Layoutflex flex-col h-screenFeste Höhe, kein Scrollen der Hülle
Editor-Splitflex flex-1 overflow-hiddenBeide Panels scrollen eigenständig
Linkes Panelflex-1 flex flex-col p-5 overflow-y-autoNimmt restliche Breite
Rechte Sidebarw-[280px] shrink-0 border-l border-brand-sand bg-white p-4 overflow-y-autoFeste Breite 280 px
Editor-Elemente
Titel-Inputw-full font-serif text-[22px] text-ink placeholder-ink-3 border-0 border-b border-transparent focus:border-brand-mint focus:outline-none pb-2 mb-4Kein Rahmen, nur Unterstrich bei Fokus
Toolbar-Buttonw-7 h-7 flex items-center justify-center rounded border border-line text-xs font-bold text-ink hover:bg-muted28 × 28 px, je B / I / ¶
Body-Textareaflex-1 w-full font-serif text-[13px] text-ink resize-none focus:outline-none min-h-[280px]contenteditable oder textarea MVP
Speicherleiste
Save-Barsticky bottom-0 border-t border-brand-sand bg-white px-5 py-3 flex items-center justify-betweenImmer sichtbar, sticky
"Entwurf speichern"rounded border border-line px-4 py-2 text-sm font-medium text-ink hover:bg-mutedGhost-Button
"Veröffentlichen"rounded bg-primary text-primary-fg px-4 py-2 text-sm font-medium hover:bg-primary/90Navy-Primär-Button
Komponenten
PersonMultiSelectWiederverwendung $lib/components/PersonMultiSelect.svelteIdentisches Pattern wie Dokument-Bearbeitung
Dokument-TypeaheadNeue Komponente $lib/components/DocumentTypeahead.svelteGET /api/documents?search=; Chips mit Titel + Datum
Validierung TitelRotes border-b border-red-500 + Fehlertext unter dem InputNur beim Submit-Versuch auslösen

W-2 — Entwurf mit Personen und Dokumenten verknüpft

US-BLOG-002
Gleiche Layout-Struktur wie W-1, aber mit ausgefülltem Titel, Fließtext und verknüpften Personen und Dokumenten. Status bleibt ENTWURF.
Desktop only · Mobile-Ansicht siehe W-4
Desktop — 1040 px
Dokumente Personen Geschichten Gespräche
MR
Der Sommer in Breslau
ENTWURF
B
I

Es war der Sommer 1927, als Franz Raddatz zum ersten Mal nach Breslau reiste. Die Briefe, die er seiner Frau Emma in diesen Wochen schickte, zeugen von einer tiefen Heimweh und gleichzeitig einer neuen Faszination für die lebhafte Stadt an der Oder.

Die Kinder warteten zu Hause in Lauban. Emma hielt die Familie zusammen, wie sie es immer getan hatte — mit Ruhe und einer unerschütterlichen Zuversicht, die aus jedem Satz ihrer Antwortbriefe sprach.

Personen
🔍
Person suchen…
Franz Raddatz × Emma Müller ×
2 Personen verknüpft
Dokumente
🔍
Dokument suchen…
Brief vom 12. Juli 1938 ×
1 Dokument verknüpft
Status
ENTWURF
Noch nicht öffentlich sichtbar.
Zuletzt gespeichert
Heute, 14:32 Uhr
von Marcel Raddatz
Alle Änderungen werden als Entwurf gespeichert.

W-3 — Veröffentlichte Geschichte bearbeiten /geschichten/[id]/edit

US-BLOG-003
Gleiche Layout-Struktur, aber Status VERÖFFENTLICHT. Die Speicherleiste zeigt andere Aktionen. Der "Löschen"-Link erscheint in der Topbar (Geschichte existiert bereits). Änderungen sind sofort live.
Desktop · Publish-Flow ist umgekehrt: Retract statt Publish
Desktop — 1040 px
Dokumente Personen Geschichten Gespräche
MR
Der Sommer in Breslau
VERÖFFENTLICHT
Löschen
B
I

Es war der Sommer 1927, als Franz Raddatz zum ersten Mal nach Breslau reiste. Die Briefe, die er seiner Frau Emma in diesen Wochen schickte, zeugen von einer tiefen Heimweh und gleichzeitig einer neuen Faszination für die lebhafte Stadt an der Oder.

Die Kinder warteten zu Hause in Lauban. Emma hielt die Familie zusammen, wie sie es immer getan hatte — mit Ruhe und einer unerschütterlichen Zuversicht, die aus jedem Satz ihrer Antwortbriefe sprach.

Personen
🔍
Person suchen…
Franz Raddatz × Emma Müller ×
Dokumente
🔍
Dokument suchen…
Brief vom 12. Juli 1938 ×
Status
VERÖFFENTLICHT
Öffentlich sichtbar für alle Leser.
Veröffentlicht am
28. April 2026
von Marcel Raddatz
Änderungen sind sofort live.

impl-ref · W-3 — Veröffentlicht-Zustand

ElementWert / KlassenAnmerkung
VERÖFFENTLICHT-Badgerounded-full bg-green-100 text-green-800 text-[10px] font-bold px-2 py-0.5 uppercase tracking-wideGrüne Pill, kein Rahmen
"Speichern"-Buttonrounded bg-primary text-primary-fg px-4 py-2 text-sm font-mediumImmer aktiv, speichert sofort live
"Zurück zu Entwurf"rounded border border-line px-4 py-2 text-sm font-medium text-amber-700 hover:bg-amber-50Setzt status=DRAFT; Bestätigung optional
"Löschen"-Linktext-sm text-red-600 font-medium hover:underlineNur sichtbar wenn Geschichte existiert; öffnet Bestätigungs-Dialog
Save-Bar-Hinweis"Änderungen sind sofort live." (xs, muted)Ersetzt den ENTWURF-Hinweis

W-4 — Mobile Ansicht /geschichten/new

US-BLOG-001 · 320 px
Gestapeltes Layout ohne horizontale Teilung. Sidebar-Inhalte werden in einem ausklappbaren Bereich unterhalb des Body-Editors angezeigt. Speicherleiste mit vertikal gestapelten Buttons.
Phone · 320 px · Sidebar kollabiert; kein Split
Phone — 320 px
9:41●●●●
MR
Neue Geschichte
ENTWURF
B
I
Schreibe hier deine Geschichte…
Personen & Dokumente
Personen
🔍
Person suchen…
Dokumente
🔍
Dokument suchen…

impl-ref · W-4 — Mobile

ElementWert / KlassenAnmerkung
Editor-Layout (Mobile)flex flex-col h-screenKein horizontaler Split
Sidebar-Collapsible<details> oder Svelte bind:openStandardmäßig geschlossen; Chevron dreht sich
Save-Bar Mobileflex flex-col gap-2 p-3 bg-white border-tButtons vertikal gestapelt, volle Breite
"Entwurf speichern" Mobilew-full rounded border border-line py-2 text-sm font-medium text-inkGhost, volle Breite
"Veröffentlichen" Mobilew-full rounded bg-primary text-primary-fg py-2 text-sm font-mediumPrimary, volle Breite, oben

W-5 — Lösch-Bestätigung (Dialog)

US-BLOG-006
Modal-Dialog über dem Editor (W-3-Zustand: veröffentlichte Geschichte). Dunkles Scrim überlagert den Editor. Dialog liegt zentriert darüber. Kein custom Dialog — Wiederverwendung des bestehenden confirm-Service.
Desktop · Ausgelöst durch Klick auf "Löschen" in der Topbar
Desktop — 1040 px (mit Modal)
Dokumente Personen Geschichten Gespräche
MR
Der Sommer in Breslau
VERÖFFENTLICHT
Löschen

Es war der Sommer 1927, als Franz Raddatz zum ersten Mal nach Breslau reiste…

Personen
Franz Raddatz
Änderungen sind sofort live.

impl-ref · W-5 — Lösch-Dialog

ElementWert / Anmerkung
Dialog-ImplementierungWiederverwendung getConfirmService() aus $lib/services/confirm.svelte.js — kein custom Dialog nötig
Scrimfixed inset-0 bg-black/40 z-40 flex items-center justify-center
Dialog-Boxbg-white rounded-lg shadow-overlay w-[400px] p-6 flex flex-col gap-4 z-50
Titelfont-serif text-[18px] font-medium text-ink
"Abbrechen"Ghost-Button — schließt Dialog, kein State-Wechsel
"Löschen"rounded bg-red-600 text-white px-4 py-2 text-sm font-medium hover:bg-red-700 — DELETE /api/geschichten/{id}, dann redirect /geschichten
Nach LöschenRedirect auf /geschichten (Index), Toast "Geschichte gelöscht"
Implementierungs-Guide für LLMs

Alle fünf Screens teilen dieselbe Svelte-Komponente GeschichteEditor.svelte. Der Unterschied zwischen /geschichten/new und /geschichten/[id]/edit liegt ausschließlich in den Load-Daten und im initialen Status-Zustand.

RouteKomponenteLoad-Funktion
/geschichten/new GeschichteEditor.svelte Kein Load nötig — leerer Zustand, status=DRAFT
/geschichten/[id]/edit GeschichteEditor.svelte GET /api/geschichten/{id} → Geschichte by id; wirft 404 wenn nicht gefunden

Technische Entscheidungen

ThemaEntscheidungBegründung
Rich-Text-Editor
MVP-Implementierung Minimales contenteditable div oder <textarea> mit document.execCommand für B/I/¶ Issue #381 erfordert nur Bold, Italic, Absatzumbrüche — keine Bibliothek, kein Bundle-Overhead
Persistenz-Format HTML-String im Backend (VARCHAR / TEXT) Einfachstes Format; bei Bedarf später auf Markdown oder ProseMirror JSON migrierbar
Personen & Dokumente
PersonMultiSelect Direktes Wiederverwenden von $lib/components/PersonMultiSelect.svelte Identisches Pattern wie im Dokument-Bearbeitungsformular — kein neues Rad erfinden
Dokument-Typeahead Neue Komponente $lib/components/DocumentTypeahead.svelte GET /api/documents?search= — gleicher Aufbau wie PersonTypeahead; Chips zeigen Titel + Datum
Permissions
Route Guard Server-seitiger Check in +page.server.ts: wenn User kein BLOG_WRITE → redirect /geschichten Niemals Editor-Controls in Lese-Ansichten zeigen; Client-seitige Prüfung reicht nicht
Autorschaft Jeder BLOG_WRITER kann jede Geschichte bearbeiten; author-Feld ist nur Anzeige Familienarchiv ist kein Blog mit privaten Drafts; Kollaboration ist erwünscht
Status-Logik
Publish-Action PATCH /api/geschichten/{id} mit { "status": "PUBLISHED" } Kein separater Endpunkt nötig — Status ist ein Feld des Modells
Retract-Action PATCH /api/geschichten/{id} mit { "status": "DRAFT" } Umkehrbar; keine separate Bestätigung (anders als Löschen)
"Löschen"-Sichtbarkeit Nur sichtbar wenn data.geschichte !== null (d.h. Edit-Route, nicht New-Route) Kein Löschen für nicht-existierende Geschichten
Löschen
Bestätigungs-Dialog Wiederverwendung getConfirmService() aus $lib/services/confirm.svelte.js Kein custom Dialog; bereits im Projekt vorhanden
Nach DELETE DELETE /api/geschichten/{id} → 204 → redirect /geschichten + Toast Standard-Muster wie bei Personen und Dokumenten
Mobile Responsive
Breakpoint Unter 640 px (sm): Split aufheben, Sidebar als Collapsible Transcribers (60+) auf Laptop/Tablet; Reader (jünger) auf Phones — Responsive für Writer ist Minor
Collapsible-Trigger "Personen & Dokumente" mit Chevron; standardmäßig geschlossen Body-Editor hat Priorität; Metadaten sind sekundär auf kleinen Bildschirmen