407 lines
25 KiB
Markdown
407 lines
25 KiB
Markdown
# Architektur
|
|
|
|
## Systemgrenze
|
|
|
|
Sendewerk ist ein einzelner SvelteKit-Prozess. Browserseiten, serverseitige
|
|
Actions, spätere REST-Endpunkte und Hintergrundaufgaben verwenden dieselbe
|
|
Geschäftslogik. SQLite und ein lokales Upload-Verzeichnis sind die einzigen
|
|
persistenten Laufzeitabhängigkeiten. Externe Mail-, RSS- oder KI-Dienste sind
|
|
nicht Voraussetzung für Kernfunktionen.
|
|
|
|
```text
|
|
Browser -> SvelteKit-Routen -> Anwendungsdienste -> SQLite
|
|
| -> Upload-Verzeichnis
|
|
+-> später: Mail/RSS über Adapter
|
|
```
|
|
|
|
Servercode lebt unter `src/lib/server` und darf nicht in Browser-Bundles
|
|
gelangen. Routen liefern nur die für ihre Ansicht erlaubten Daten. Fachlogik
|
|
wird nicht an Svelte-Komponenten gebunden, damit Browseraktionen und die
|
|
spätere API dieselben Regeln verwenden.
|
|
|
|
## Laufzeit und Speicher
|
|
|
|
- Lokal: `.data/sendewerk.sqlite` und `.data/uploads`, jeweils konfigurierbar.
|
|
- Tests: pro Lauf ein temporäres Verzeichnis unter dem Betriebssystem-Tempdir.
|
|
- Cloudron: `/app/data/sendewerk.sqlite` und `/app/data/uploads`.
|
|
- Migrationen liegen nummeriert unter `migrations/`, laufen in Transaktionen
|
|
und werden in `schema_migrations` protokolliert.
|
|
- Der Seed nutzt stabile IDs und Transaktionen. Wiederholung liefert denselben
|
|
fachlichen Ausgangsbestand, ohne Migrationstabellen anzutasten.
|
|
- Zeitpunkte werden als ISO-8601-UTC-Text gespeichert. Dauern sind nichtnegative
|
|
ganze Sekunden. Zeitzonendarstellung ist `Europe/Berlin`.
|
|
|
|
## Gesamtdatenmodell
|
|
|
|
Die ersten sechs Etappen materialisieren Identität, Inhaltsverwaltung,
|
|
Sendungsplanung, Live-Durchführung, Historie und Lesefreigaben. Folgende Grenzen
|
|
gelten für umgesetzte und spätere Bereiche.
|
|
|
|
| Bereich | Geplante Entitäten und Beziehungen |
|
|
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Identität | `users`, Rollen, Sessions, Einladungen, Reset-Tokens und Sperren sind umgesetzt; technische Zugänge folgen später |
|
|
| Taxonomie | getrennte `section_categories`, `formats`, `topics` samt optionalem Themenicon; Pieces und Vorschläge mehrfach verknüpft |
|
|
| Inhalt | `pieces` mit Status, Soft Delete, Favorit, Revision, Texten, Quellen, Werkzeugen und privaten Medien; Vorlagen speichern Snapshots |
|
|
| Sendungsbau | `shows`, geordnete `show_sections`, `piece_usages` samt Quellen-/Werkzeug-/Themen-/Mediensnapshots, Reservebank und Vorlagen |
|
|
| Live | expliziter Live-Stand, aktuelles Element, tatsächliche Piece-Zeiten, persistente Abschnitts-/Gesamttimer und getrennte Präsentationsausgabe |
|
|
| Historie | unveränderliche Abschluss-Snapshots und Verwendungshistorie; Übersprungenes zählt nicht als gesendet |
|
|
| Freigaben | widerrufbare, gehashte Lesetokens mit Sendung, Umfang, Passwortoption und Ablaufzeit |
|
|
| RSS | Feedquellen, Abrufläufe und externe Einträge mit stabiler Herkunfts-ID sowie Status neu/übernommen/verworfen |
|
|
| API/Audit | technische Berechtigungen, Idempotenzschlüssel und Audit-Ereignisse mit Akteur, Aktion und Ziel |
|
|
| Betrieb | Anwendungseinstellungen und spätere Job-Zustände, keine Geheimnisse im Klartext ohne begründete Schutzstrategie |
|
|
|
|
### Historien- und Kopierregel
|
|
|
|
Beim Einplanen entsteht eine bearbeitbare `piece_usage`-Kopie der
|
|
sendungsrelevanten Texte und Dauer. Das Original bleibt verknüpft, wird aber
|
|
nicht live gespiegelt. Beim Abschluss wird der damalige Sendungsinhalt stabil
|
|
eingefroren. Eine historische Sendung wird dupliziert, nie weiterbearbeitet.
|
|
|
|
### Dauerregel
|
|
|
|
Eine Abschnittsdauer ist entweder die Summe ihrer eingeplanten Verwendungen
|
|
oder eine explizite Abschnittsdauer. Beides wird nie addiert. Reserveelemente
|
|
zählen erst nach dem Einplanen. Geplante und tatsächliche Dauer bleiben
|
|
getrennt.
|
|
|
|
Ein Live-Abschnittstimer entsteht ausschließlich beim tatsächlichen Eintritt in
|
|
einen Abschnitt mit positiver expliziter Dauer. Er speichert dieses Soll als
|
|
unveränderlichen Wert und summiert eigene Laufintervalle; Piece-Wechsel im
|
|
selben Abschnitt erzeugen deshalb weder Reset noch doppelte Piece-Laufzeit.
|
|
Der Gesamttimer speichert ebenfalls mehrere Laufintervalle. Eine gemeinsame
|
|
Pause schließt die offenen Piece-, Abschnitts- und Gesamtintervalle; Fortsetzen
|
|
öffnet neue Intervalle, ohne die Pause als Laufzeit zu zählen. Abschnitts- und
|
|
Gesamtreset ersetzen ausschließlich die jeweilige Zeitbasis.
|
|
|
|
## Anmeldung und Rechte
|
|
|
|
Etappe 2 ergänzt lokale Konten mit den festen Rollen `barkeeper` und
|
|
`thekencrew`. Passwörter werden mit Argon2id gespeichert. Sitzungs-, Einladungs-
|
|
und Reset-Tokens entstehen aus kryptografischem Zufall; in SQLite liegt jeweils
|
|
nur ihr SHA-256-Hash. Der Browser erhält ausschließlich das rohe Sitzungstoken
|
|
in einem `HttpOnly`-, `SameSite=Lax`-Cookie.
|
|
|
|
`hooks.server.ts` löst die Sitzung für jeden Request auf. Die Datenbankabfrage
|
|
verbindet die Session immer mit dem aktuellen Nutzerzustand, sodass Sperren und
|
|
Rollenwechsel ohne verzögerte Token-Erneuerung wirken. Geschützte Routen
|
|
erfordern eine Anmeldung; Verwaltungs-Loads und jede Verwaltungs-Action prüfen
|
|
zusätzlich die Barkeeper-Rolle. Sperrung und Passwortwechsel löschen bestehende
|
|
Sessions transaktional.
|
|
|
|
Der Erstadmin benötigt sowohl eine leere Nutzertabelle als auch ein extern
|
|
konfiguriertes Setup-Token. Einladungs- und Reset-Links sind 24 Stunden gültig,
|
|
werden erst beim bestätigenden POST verbraucht und sind genau einmal nutzbar.
|
|
Fehlgeschlagene Loginversuche werden pro E-Mail-Schlüssel und Clientadresse in
|
|
einem Zeitfenster begrenzt, ohne das Konto dauerhaft zu sperren.
|
|
|
|
Mail verwendet einen Adapter: `local` schreibt geschützte Dateien in den
|
|
konfigurierten Outbox-Pfad, `smtp` nutzt Nodemailer nur nach expliziter Auswahl.
|
|
Die feste Anwendungs-Origin erzeugt Links und bestimmt den `Secure`-Status der
|
|
Cookies; sie wird nicht aus ungeprüften Request-Headern übernommen.
|
|
|
|
## Inhalte, Markdown und Medien
|
|
|
|
Etappe 3 führt die Inhaltslogik in `src/lib/server/content.ts` zusammen. Listen
|
|
erhalten ausschließlich kompakte Piece-Daten; private Recherche und vollständige
|
|
Redaktionstexte werden nur an den angemeldeten Editor ausgeliefert. Themen und
|
|
Abschnittskategorien sind Mehrfachzuordnungen, das Format bleibt eine einzelne
|
|
unabhängige Zuordnung. Quellen werden gemeinsam mit einem Piece atomar ersetzt.
|
|
`is_favorite` ist eine globale Eigenschaft des Originals und keine Nutzung oder
|
|
Historienmarkierung. Der Werkzeugkatalog liegt getrennt von Formaten; Formate
|
|
sind nur Eignungszuordnungen. `piece_tools` hält pro Piece den privaten
|
|
Arbeitslink und eine Notiz, während Deaktivierung das Katalogobjekt und alle
|
|
bestehenden Zuordnungen erhält. Ein serverseitig validiertes `icon_name` gehört
|
|
zum Werkzeugkatalog und wird im Originaleditor angezeigt, ohne bestehende
|
|
Usage-Snapshots nachträglich zu verändern.
|
|
Ein Piece kann für die Präsentation Text oder genau eine vorhandene Quelle als
|
|
Linkkarte beziehungsweise Video auswählen. Abgerufene Metadaten und Kennzeichen
|
|
für manuell korrigierte Felder liegen am Piece; ein erneuter Abruf überschreibt
|
|
diese Felder nicht.
|
|
|
|
Piece-Vorlagen liegen als validierte JSON-Snapshots in `piece_templates`. Jeder
|
|
Speichervorgang kopiert den vollständigen Vorlageninhalt tief und erhöht bei
|
|
einer Aktualisierung die Revision; spätere Änderungen am Ausgangs-Piece wirken
|
|
nicht auf die Vorlage zurück. Die leere Vorlage bleibt virtuell. Interner Titel
|
|
und optionaler Präsentationstitel sind getrennte Felder; ohne öffentlichen Titel
|
|
verwendet die Präsentationsprojektion den internen Titel als Fallback.
|
|
|
|
Jedes Piece besitzt eine ganzzahlige Revision. Speichern, Archivieren und
|
|
Medienänderungen verwenden ein bedingtes Update auf die erwartete Revision.
|
|
Eine veraltete Bearbeitung liefert einen Konflikt und verändert weder Piece noch
|
|
Zuordnungen. Archivieren merkt sich den vorherigen Arbeitsstatus für die
|
|
Wiederherstellung. Duplikate erhalten neue IDs und unabhängige Bilddateien.
|
|
Piece-Dauern werden in den Fundus- und Verwendungseditoren als Minuten erfasst,
|
|
an der Servergrenze auf volle Sekunden gerundet und ausschließlich als ganze
|
|
Sekunden persistiert.
|
|
|
|
Markdown wird roh gespeichert, mit Marked serverseitig gerendert und danach mit
|
|
sanitize-html auf eine kleine Allowlist reduziert. Bilder werden nicht über
|
|
Markdown eingebettet. Uploads akzeptieren höchstens 5 MB und nur anhand ihrer
|
|
Dateisignatur erkannte JPEG-, PNG-, WebP- oder GIF-Dateien. Interne Dateinamen
|
|
sind zufällige IDs; Originalnamen werden nie als Pfad verwendet. `/media/[id]`
|
|
prüft die Anmeldung und liefert private Antworten mit `no-store`, `nosniff`,
|
|
Same-Origin-Ressourcenrichtlinie und restriktiver CSP aus.
|
|
|
|
Ein Piece kann genau eines seiner aktiven Medien ausdrücklich als
|
|
Präsentationsbild auswählen. Die Auswahl liegt in `piece_presentation_media`;
|
|
das Entfernen setzt am Medium einen Zeitstempel statt die Datei zu löschen.
|
|
Damit bleiben bereits erzeugte `usage_media_snapshots` auflösbar, während neue
|
|
Einplanungen und der Originaleditor nur aktive Medien berücksichtigen. Das
|
|
Soft Delete eines Pieces setzt ebenfalls nur `deleted_at_utc`: normale Listen
|
|
und neue Einplanungen schließen es aus, bestehende Verwendungsreferenzen und
|
|
Snapshots bleiben unverändert erhalten.
|
|
|
|
## Sendungsbau
|
|
|
|
Etappe 4 bündelt alle Planungsmutationen in `src/lib/server/shows.ts`. Eine
|
|
Sendung ist die Konflikt- und Transaktionsgrenze: Jede Änderung an Metadaten,
|
|
Abschnitten, Verwendungen oder Reihenfolge vergleicht die erwartete Revision und
|
|
erhöht sie atomar. Abgeschlossene Sendungen sind bereits an dieser Grenze gegen
|
|
weitere Bearbeitung geschützt.
|
|
|
|
Beim Einplanen werden Titel, Dauer, Einleitung, Recherche, Moderation,
|
|
Interaktion, Überleitung und der vollständige Präsentationszustand aus dem sendefertigen
|
|
Piece kopiert. Dazu gehören Modus, ausgewählte Quelladresse samt Zeitmarke,
|
|
Metadaten und validierte Anbieter-ID. Zusätzlich speichern eigene geordnete
|
|
Tabellen alle Quellen, Werkzeugnamen samt Start-/Arbeitslink und Notiz, die
|
|
Themennamen samt Icon sowie das ausdrücklich ausgewählte Präsentationsmedium.
|
|
Eigene Notizen liegen ausschließlich an
|
|
der Verwendung. Die Referenz zum Original bleibt für Historie und
|
|
Nachvollziehbarkeit erhalten, wird aber nicht live gespiegelt. Eine explizite
|
|
Aktualisierung aus dem Fundus ersetzt Titel, Dauer, alle fünf Redaktionstexte
|
|
und den Präsentationszustand atomar aus dem referenzierten Original. Eigene
|
|
Notizen, Status und vorhandene Live-Intervalle werden dabei nicht verändert;
|
|
fehlt das Original oder ist die Sendungsrevision veraltet, wird die gesamte
|
|
Mutation verworfen.
|
|
Eine Einplanung zu duplizieren kopiert genau diesen Snapshot mit derselben
|
|
Original-ID direkt hinter das Ausgangselement. Die neue Verwendung erhält den
|
|
Status `open`, eine neue ID und damit weder Live-Referenzen noch Zeitintervalle.
|
|
|
|
Die Verwendungshistorie wird nicht separat gepflegt. Sie wird über die
|
|
Original-ID aus Verwendungen mit Status `done` in abgeschlossenen Sendungen
|
|
abgeleitet; Reservebank und alle anderen Verwendungs- oder Sendungsstatus sind
|
|
ausgeschlossen. Über dieselbe Original-ID wird unabhängig davon ermittelt, ob
|
|
mindestens eine Verwendung in einer noch nicht abgeschlossenen Sendung liegt.
|
|
Der allgemeine Fundus blendet diesen globalen Kontext standardmäßig aus. Die
|
|
Quellspalte einer Sendung betrachtet für ihren separaten Einplanungsfilter nur
|
|
die aktuelle Sendung einschließlich Reservebank.
|
|
|
|
Jede Sendung besitzt intern genau einen ausgeblendeten Reserveabschnitt. Dadurch
|
|
verwenden Plan und Reserve dieselbe geprüfte Verwendungslogik, während die
|
|
Reserve in der öffentlichen Struktur und allen Dauerberechnungen getrennt
|
|
bleibt. Positionen werden nach jeder Transaktion kollisionsfrei normalisiert.
|
|
Eine explizite Abschnittsdauer ersetzt die Summe der Verwendungen vollständig.
|
|
|
|
Sendungsvorlagen und ihre Abschnitte sind eigene Tabellen. Beim Anlegen werden
|
|
die Abschnitte mit neuen IDs kopiert; spätere Änderungen an der Sendung können
|
|
die Vorlage deshalb nicht verändern.
|
|
|
|
Die Sendungsübersicht leitet Liste, Desktop-Monatsraster und mobile Monatsagenda
|
|
aus derselben `listShows()`-Abfrage ab. UTC-Zeitpunkte werden erst für Gruppierung
|
|
und Anzeige nach `Europe/Berlin` in zivile Kalendertage übersetzt. Sendungen ohne
|
|
Startzeit bleiben in einem eigenen Bereich sichtbar. `users.show_overview_view`
|
|
speichert ausschließlich die kontobezogene Darstellungspräferenz; der gewählte
|
|
Monat bleibt URL-Zustand und verändert keine Sendungsdaten.
|
|
|
|
Die Direktanlage aus einer Sendung erzeugt Piece und Verwendung innerhalb einer
|
|
verschachtelbaren SQLite-Transaktion. `planning_commands` speichert die
|
|
Befehls-ID und das Ergebnis, sodass ein wiederholter Request dieselben IDs
|
|
zurückgibt und weder ein zweites Piece noch eine zweite Einplanung erzeugt.
|
|
|
|
Die Editorformulare bleiben progressive SvelteKit-Form-Actions, werden im
|
|
Browser aber mit `use:enhance` gesendet. Der Verwendungseditor liegt in einem
|
|
nativen Dialog, den ein beschrifteter Icon-Button öffnet. Dadurch bleibt das
|
|
Dokument bei Planungsmutationen bestehen; nur die serverseitigen Seitendaten
|
|
werden aktualisiert. Ladeanzeige und FLIP-Animation liegen am betroffenen
|
|
Abschnitt oder an der betroffenen Verwendung. Eindeutige Drag-Griffe übertragen
|
|
entweder eine Quellenkopie, einen Abschnitt oder eine Verwendung auf dieselben
|
|
serverseitig geprüften Actions wie die alternativen Schaltflächen. Während des
|
|
Ziehens bleibt das Original transparent an seinem Ausgangsort; eine flüchtige
|
|
Vorschau in der Liste belegt bereits die anhand der Zeigerposition berechnete
|
|
Zielhöhe und verdrängt dadurch die benachbarten Elemente. Persistiert wird erst
|
|
beim Loslassen.
|
|
|
|
Die Ausschlussfilter für gesendete und bereits eingeplante Pieces arbeiten
|
|
clientseitig auf der serverseitig berechneten Kontextmenge. Ihr Zustand wird
|
|
ohne Navigation in die URL übernommen und bei nachfolgenden Suchfiltern
|
|
mitgesendet. Planmutationen invalidieren anschließend die Seitendaten, sodass
|
|
das Entfernen der letzten Einplanung ein Original unmittelbar wieder anbietet.
|
|
|
|
## Live-Modus und Präsentationsgrenze
|
|
|
|
`src/lib/server/live.ts` ist die Transaktionsgrenze der Live-Durchführung. Der
|
|
Live-Stand besitzt eine eigene Revision. Jeder steuernde Befehl trägt zusätzlich
|
|
eine vom Browser erzeugte Befehls-ID; `(show_id, command_id)` ist eindeutig und
|
|
liefert bei Wiederholung das bereits gespeicherte Ergebnis. Tatsächliche Zeiten
|
|
liegen als mehrere Start-/Endintervalle je Verwendung vor. Damit bleiben
|
|
Zurückgehen, Wiederaufnahme und bereits abgeschlossene Laufzeit getrennt von der
|
|
geplanten Dauer nachvollziehbar.
|
|
|
|
`live_show_timer_runs` hält die unabhängige Gesamtzeit. Pause und Fortsetzen
|
|
behandeln Gesamt-, Abschnitts- und Piece-Intervalle gemeinsam. Beenden schließt
|
|
laufende Intervalle und setzt `ended_at_utc`, finalisiert die Sendung aber noch
|
|
nicht. Eine ausdrückliche Wiederaufnahme bewahrt Zeiten und Fortschritt; nur
|
|
„Neuer Durchlauf“ verwirft Laufintervalle und setzt die Piece-Status zurück. Die
|
|
Restzeitschätzung verwendet je Abschnitt entweder die positive Abschnittsdauer
|
|
oder die verbleibenden Piece-Dauern und zählt beide nie zusammen.
|
|
|
|
Die interne Route `/live` benötigt weiterhin eine aktive Sitzung und darf
|
|
Recherche, Moderation und eigene Arbeitsdaten lesen. Die separate Ausgabe nutzt
|
|
den stabilen Kanal `/heinzlive/default` in einem reduzierten Layout ohne
|
|
Navigation oder Nutzerdaten. Der geheime 256-Bit-Schlüssel steht nur im
|
|
URL-Fragment und wird per POST gegen ein `HttpOnly`-, `SameSite=Strict`-Cookie
|
|
getauscht. SQLite speichert seinen SHA-256-Hash für die Zugangsprüfung sowie den
|
|
rohen Schlüssel, weil die angemeldete Steuerung dieselbe stabile OBS-Adresse
|
|
erneut ausgeben können muss. Datenbank und Sicherungen sind deshalb Teil der
|
|
Geheimnisgrenze. Eine Rotation widerruft den bisherigen Schlüssel, normales
|
|
erneutes Öffnen behält ihn bei.
|
|
Das private Markdownfeld „Interaktion“ wird am Original und als
|
|
Verwendungssnapshot gespeichert und ausschließlich in der authentifizierten
|
|
Live-Regie gerendert. Page, Polling-JSON und Proxyrouten leiten ihre Daten aus einer eigenen
|
|
Positivliste ab. Private Quellen-/Werkzeug-Snapshots, Arbeitslinks, Notizen und
|
|
Uploads werden nicht veröffentlicht; nur Themennamen sowie die ausdrücklich
|
|
gewählte Link- oder Videoquelle und deren erlaubte Vorschau-URL können die
|
|
Grenze passieren.
|
|
Lokale Betrachtungsnavigation, Fortschaltung oder Wiederaufnahme verändern den
|
|
Präsentationsstand nicht. Zurück und die Auswahl einer Ablaufzeile wechseln nur
|
|
den im Browser betrachteten Verwendungssnapshot; tatsächliches Live-Piece,
|
|
Status, Timer und Ausgabe bleiben serverseitig unverändert. Die öffentliche
|
|
Projektion liest ausschließlich den ausdrücklich präsentierten
|
|
Verwendungssnapshot; spätere Änderungen am Original erscheinen erst nach einer
|
|
ausdrücklichen Snapshot-Aktualisierung.
|
|
|
|
Linkmetadaten werden nur auf ausdrückliche Aktion durch
|
|
`src/lib/server/presentation.ts` geladen. Jeder HTTP(S)-Abruf löst den Zielhost
|
|
serverseitig auf, sperrt lokale und private IPv4-/IPv6-Ziele und bindet den
|
|
Request an die geprüfte Adresse. Dieselbe Prüfung gilt nach höchstens drei
|
|
Redirects. Zeit- und Größenlimits begrenzen HTML, Anbieter-Metadaten und Bilder.
|
|
Externe Vorschaubilder gelangen nur über token- beziehungsweise
|
|
sitzungsgebundene Proxyrouten in die Ausgabe; fremdes HTML und fremde Skripte
|
|
werden nie übernommen.
|
|
|
|
Videoquellen werden aus der gespeicherten URL erneut auf eine streng validierte
|
|
YouTube- oder TikTok-ID reduziert. Der Browser erhält nur daraus erzeugte
|
|
offizielle Embed-URLs, startet sie nicht automatisch und ersetzt das Iframe bei
|
|
jedem Inhaltswechsel. YouTube erhält die Startsekunde als Embed-Parameter;
|
|
TikTok wird nach dessen Bereitschaft über die offizielle Player-`postMessage`-
|
|
Schnittstelle positioniert. Nicht unterstützte oder unvollständige Videoquellen
|
|
fallen auf eine normale Linkkarte zurück. Eine authentifizierte Testansicht
|
|
verwendet denselben Renderer, schreibt aber keinen Live-Zustand.
|
|
|
|
Die Ausgabe synchronisiert ihren kleinen öffentlichen Zustand per
|
|
`no-store`-Polling. Der letzte gültige Inhalt bleibt bei einem Verbindungsfehler
|
|
sichtbar; die Oberfläche kennzeichnet den Abbruch und versucht die Verbindung
|
|
mit verlangsamtem Intervall erneut. Private Live-Daten werden nicht Teil dieses
|
|
Zustandsobjekts.
|
|
|
|
## Abschluss, Historie und Lesefreigaben
|
|
|
|
`src/lib/server/completion.ts` ist die Transaktionsgrenze des endgültigen
|
|
Sendungsabschlusses. Er verlangt einen beendeten Live-Lauf sowie passende Show-
|
|
und Live-Revisionen. Eine Befehls-ID in `show_finalizations` macht Wiederholungen
|
|
idempotent. In derselben Transaktion werden offene Intervalle geschlossen,
|
|
Abschnittskategorien eingefroren, ausschließlich erledigte Verwendungen außerhalb
|
|
der Reservebank in `piece_usage_history` materialisiert, bewusst gewählte
|
|
Fundus-Originale archiviert und die Sendung auf `completed` gesetzt. Jede normale
|
|
Planungs- und Live-Mutation sperrt diesen Zustand.
|
|
|
|
Die eigentlichen Sendungstexte, Quellen, Werkzeuge, Themen und Medien liegen
|
|
weiterhin in den beim Einplanen erzeugten Usage-Snapshots. Finalisierung friert
|
|
die zuvor noch katalogabhängige Abschnittskategorie ein. Abgeschlossene
|
|
Sendungen werden ausschließlich gelesen oder vollständig in einen neuen Entwurf
|
|
dupliziert. Das Duplikat erhält neue Show-, Abschnitts-, Usage- und Relations-IDs,
|
|
offene Usage-Status und weder Live-, Historien- noch Freigabedaten.
|
|
|
|
`src/lib/server/shares.ts` erzeugt für Barkeeper zufällige 256-Bit-Lesetokens;
|
|
SQLite speichert nur SHA-256-Hashes. Eine Freigabe verweist direkt auf eine
|
|
Sendung und darf deshalb bereits im Entwurf, im bereiten oder im laufenden
|
|
Zustand entstehen. Der Umfang ist eine Positivliste aus `rundown`, `moderation`
|
|
oder `research`. Optionale Passwörter liegen als Argon2id-Hash vor. Nach
|
|
erfolgreicher Prüfung entsteht eine höchstens zwölf Stunden gültige, ebenfalls
|
|
nur gehashte Gast-Sitzung; Fehlversuche werden pro Link und Clientadresse
|
|
begrenzt. Änderungen an Umfang, Passwort oder Ablauf invalidieren bestehende
|
|
Gast-Sitzungen. Ablauf und Löschung werden bei jedem HTML-, Seitendaten- und
|
|
Medienrequest erneut geprüft.
|
|
|
|
Die öffentliche Route `/freigabe/[token]` erhält keine interne Sendungs-ID und
|
|
keinen ungefilterten `Show`-Datensatz. Sie projiziert genau die am Token gebundene
|
|
Sendung und nur die Felder ihres Umfangs. Vor dem Abschluss werden aktuelle
|
|
Sendungs- und Usage-Snapshots bei jedem Aufruf neu gelesen; danach liefert
|
|
dieselbe Projektion die durch die Finalisierung stabilisierten Snapshots. Die
|
|
separate Medienroute akzeptiert ausschließlich das ausdrücklich gewählte
|
|
Präsentationsmedium einer eingeplanten Nicht-Reserve-Verwendung derselben
|
|
Sendung. Private `/media`-Dateien bleiben weiterhin an eine Redaktionssitzung
|
|
gebunden.
|
|
|
|
Die wiederverwendbare Barkeeper-Komponente `ShareManagement.svelte` liegt direkt
|
|
in der Sendungsbearbeitung und in der Verwaltung. Beide Routen verwenden
|
|
dieselben Servicefunktionen für Anlage, Bearbeitung und Löschung; die
|
|
Sendungsroute bindet Mutationen zusätzlich an ihre eigene Sendungs-ID. Die
|
|
Verwaltung gruppiert bestehende Freigaben nach Sendung und zeigt deren aktuellen
|
|
Status und Termin.
|
|
|
|
## Oberflächenstruktur
|
|
|
|
Die Hauptnavigation umfasst Eingang, Fundus, Sendungen, Archiv und Verwaltung.
|
|
Auf dem Desktop bleibt eine feste Seitenleiste sichtbar; Verwaltung liegt an
|
|
ihrem unteren Ende. Bis einschließlich `52rem` ersetzen eine obere Leiste und
|
|
die untere Hauptnavigation die Seitenleiste vollständig. Auf breiten Ansichten
|
|
stehen Fundus und Ablauf nebeneinander. Bis einschließlich `72rem` werden sie
|
|
über eine gut erreichbare Umschaltung einzeln gezeigt, damit auch schmale
|
|
Desktopfenster ohne horizontalen Überlauf bedienbar bleiben.
|
|
Die Sendungsübersicht schaltet über zwei gleichwertige Formularaktionen zwischen
|
|
Liste und Kalender um. Auf breiten Ansichten erscheint ein siebenspaltiges
|
|
Monatsraster ohne Termin-Drag-and-drop; bis `52rem` ersetzt es eine lineare,
|
|
touchfreundliche Agenda desselben Monats.
|
|
Der Live-Modus ist eine eigene, fokussierte Arbeitsansicht. Die
|
|
Präsentationsausgabe verwendet eine separate, strikt reduzierte Datenansicht
|
|
ohne interne Anwendungsnavigation.
|
|
|
|
Die private Route `/live` verwendet ebenfalls einen eigenen authentifizierten
|
|
Layoutmodus ohne allgemeine Seitenleiste, mobilen App-Header oder
|
|
Bottomnavigation. Ihre Regiefläche belegt die verfügbare Viewporthöhe als Raster
|
|
aus fester Statuszeile, unabhängig scrollender Piece- und Ablaufspalte sowie
|
|
fester Aktionszeile. Die Breite entscheidet über zwei Spalten oder das mobile
|
|
Sheet; eine geringe Desktophöhe verdichtet zusätzlich Abstände und Leisten,
|
|
ohne Schrift oder Oberfläche zu skalieren. Unterhalb des schmalen Breakpoints
|
|
wird ausschließlich die Ablaufspalte als modales Sheet mit Fokusbegrenzung
|
|
eingeblendet; beim Wechsel auf einen breiteren Viewport wird dieser Modalzustand
|
|
aufgehoben. Die lokale Betrachtungsnavigation ist keine Live-Action; erst
|
|
Präsentieren, Überspringen und Erledigen schreiben den jeweiligen getrennten
|
|
Zustand. Die öffentliche `/heinzlive`-Projektion bleibt unverändert. Lokale
|
|
Lesepositionen sind an die jeweilige Verwendung gebunden und werden nicht
|
|
zwischen Pieces übertragen.
|
|
|
|
Der Live-Kopf besteht aus zwei regulären Rasterzeilen. Abschnitt und folgendes
|
|
Piece sind einzeilig gekürzte, fokussierbare Schaltflächen, die ihren vollständigen
|
|
Text lokal aufklappen. Das Regiemenü wird bei Bedarf als fest am Viewport
|
|
positionierter Dialog neben dem Kopf gerendert. Dadurch unterliegt es weder dem
|
|
Overflow der Regiefläche noch dem durch Glasfilter erzeugten Stacking Context;
|
|
seine maximale Höhe bleibt an den Viewport gebunden und intern scrollbar.
|
|
|
|
Icons werden aus dem lokalen Font-Awesome-Classic-Regular-Sprite unter
|
|
`static/icons` gerendert. Die Verwaltung lädt zusätzlich die durchsuchbaren
|
|
Metadaten aller 3.671 Icons, zeigt je Suchergebnis aber höchstens 120 Einträge
|
|
gleichzeitig. Allgemeine Seiten wie Login oder Navigation laden den Katalog
|
|
nicht in ihren Client-Chunk.
|
|
|
|
Der eigenständige Piece-Editor ordnet denselben Formvertrag als redaktionelles
|
|
Arbeitsblatt an. Titel, Fundstück, private Recherche sowie Einleitung,
|
|
Moderationsstichpunkte und Überleitung bilden die primäre Schreibfläche.
|
|
Quellen, Einordnung, Präsentation und eine Bildzusammenfassung liegen auf breiten
|
|
Ansichten in einer kontextuellen Seitenleiste; auf kleinen Ansichten öffnen
|
|
dieselben DOM-Formfelder als fokussierbare Werkzeugflächen und werden dabei nicht
|
|
neu erzeugt oder ausgehängt. `EditorialMarkdownField` hält Lese- und
|
|
Bearbeitungszustand je Textfeld lokal, während alle Werte weiterhin gemeinsam
|
|
über die bestehende `save`-Action und Piece-Revision gespeichert werden. Die
|
|
separate Bild-Action und alle Präsentationsactions bleiben eigene serverseitige
|
|
Mutationen.
|
|
|
|
## Sicherheitsgrenzen
|
|
|
|
Authentifizierung und Autorisierung werden seit Etappe 2 serverseitig an jeder
|
|
betroffenen Route und Action geprüft. Markdown und fremdes HTML werden vor
|
|
Ausgabe bereinigt. Uploadzugriff folgt denselben Rechten. Netzwerkabrufe für
|
|
Präsentationsmetadaten und Vorschaubilder schützen interne Ziele auch nach
|
|
DNS-Auflösung und Redirects und besitzen Größen-, Zeit- und Redirectlimits.
|