521 lines
28 KiB
Markdown
521 lines
28 KiB
Markdown
# Entscheidungen
|
|
|
|
Stand der Quellenprüfung: 22. Oktober 2026. Verwendet wurden offizielle
|
|
Dokumentationen und die npm-Registry für konkrete Paket-Metadaten.
|
|
|
|
## D-001: SvelteKit 3 und Node-Unterstützung
|
|
|
|
Die Anwendung verwendet SvelteKit 3, Svelte 5, TypeScript und Vite 8. Das
|
|
Projekt wurde gemäß aktueller Empfehlung mit `npx sv create` angelegt. Die
|
|
Kit-Konfiguration liegt bei SvelteKit 3 in `vite.config.ts`, nicht in einer
|
|
`svelte.config.js`.
|
|
|
|
Die lokale Mindestversion ist Node 22.17. Das Cloudron-Image nutzt Node 24 LTS.
|
|
Der vorhandene lokale Lauf wurde mit Node 22.23.1 geplant.
|
|
|
|
Quellen:
|
|
|
|
- <https://svelte.dev/docs/kit/creating-a-project>
|
|
- <https://svelte.dev/docs/kit/migrating-to-sveltekit-3>
|
|
- <https://svelte.dev/docs/cli/sv-create>
|
|
- npm-Metadaten für `@sveltejs/kit` 3.1.0 am 21.10.2026
|
|
|
|
## D-002: Offizieller Node-Adapter
|
|
|
|
`@sveltejs/adapter-node` erzeugt den Produktionsserver in `build/`. Gebaut wird
|
|
mit `npm run build`, gestartet mit `node build`. `HOST` und `PORT` konfigurieren
|
|
den Server. Der Adapter behandelt SIGTERM/SIGINT und kontrolliertes Herunter-
|
|
fahren; ein eigener Server ist für Etappe 1 nicht erforderlich.
|
|
|
|
Quelle: <https://svelte.dev/docs/kit/adapter-node>
|
|
|
|
## D-003: SQLite mit better-sqlite3
|
|
|
|
`better-sqlite3` 13.x wird als gekapselter synchroner Treiber verwendet. Das
|
|
Paket ist MIT-lizenziert und verlangt Node 22 oder neuer. Gegenüber
|
|
`node:sqlite` wird damit in Etappe 1 keine API verwendet, die in der offiziellen
|
|
Node-24-Dokumentation noch als Release Candidate gekennzeichnet ist. Die
|
|
Datenbankgrenze bleibt in `src/lib/server` gekapselt.
|
|
|
|
Migrationen sind eigene nummerierte SQL-Dateien und laufen transaktional. WAL
|
|
wird in Etappe 1 nicht aktiviert; für die kleine Redaktion ist das
|
|
Rollback-Journal zunächst ausreichend und vereinfacht die frühe
|
|
Betriebsprüfung.
|
|
|
|
Quellen:
|
|
|
|
- <https://nodejs.org/docs/latest-v24.x/api/sqlite.html>
|
|
- <https://www.sqlite.org/lang_transaction.html>
|
|
- npm-Metadaten für `better-sqlite3` 13.0.3 am 21.10.2026
|
|
|
|
## D-004: Cloudron-Paket
|
|
|
|
Das Paket nutzt `manifestVersion: 2`, internen HTTP-Port 3000 und `/healthz`.
|
|
`localstorage.sqlite.paths` nennt `/app/data/sendewerk.sqlite`, damit Cloudron
|
|
eine konsistente Sicherung der aktiven SQLite-Datei erstellen kann. Das
|
|
Startskript stellt Verzeichnisse und Besitzrechte her, migriert als Benutzer
|
|
`cloudron` und ersetzt sich zuletzt per `exec` durch den Node-Prozess.
|
|
|
|
Das Image ist auf die am 21.10.2026 offiziell dokumentierte datierte Node-24-
|
|
Basis samt Digest gepinnt. Außer `/app/data`, `/run` und `/tmp` wird das
|
|
Laufzeitdateisystem als read-only behandelt.
|
|
|
|
Quellen:
|
|
|
|
- <https://docs.cloudron.io/packaging/>
|
|
- <https://docs.cloudron.io/packaging/manifest/>
|
|
- <https://docs.cloudron.io/packaging/addons/#localstorage>
|
|
- <https://docs.cloudron.io/packaging/cheat-sheet/>
|
|
|
|
## D-005: Theme ohne serverseitiges Nutzerkonto
|
|
|
|
Etappe 1 speichert `light`, `dark` oder `system` in `localStorage`. Ein kurzes
|
|
Inline-Skript in `app.html` setzt das resultierende Theme vor dem ersten Paint
|
|
auf dem Wurzelelement. Dies vermeidet einen falschen Theme-Blitz und benötigt
|
|
vor Etappe 2 weder Cookie noch Konto. Die Einstellung bleibt bei Neuladen im
|
|
gleichen Browser erhalten.
|
|
|
|
## D-006: Etappe-1-Vorschauen statt Attrappen
|
|
|
|
Vorschauseiten lesen echte persistente SQLite-Seed-Daten, bieten aber keine
|
|
funktionslosen Speichern- oder Live-Schaltflächen. Ein sichtbarer Hinweis
|
|
grenzt Gestaltungsvorschauen von den späteren Fachfunktionen ab. Damit wird
|
|
keine noch nicht implementierte Funktion als fertig dargestellt.
|
|
|
|
## D-007: Arbeitsansicht nach verfügbarem Platz
|
|
|
|
Die feste Desktop-Seitenleiste wird bis `52rem` verwendet und dann vollständig
|
|
durch mobile Kopf- und Fußnavigation ersetzt. Der Sendungsbau schaltet bereits
|
|
bis `72rem` von zwei Spalten auf eine bedienbare Pane-Umschaltung um. Dadurch
|
|
bleibt die Desktopnavigation in schmalen Fenstern erhalten, ohne Fundus und
|
|
Ablauf zusammenzudrücken.
|
|
|
|
Der Fundus erscheint auf dem Desktop als kompakte Liste und auf kleinen
|
|
Bildschirmen als Kartenfolge. Vorschau- und Schreibschutzinformationen sind in
|
|
einem nativen, tastaturbedienbaren `details`-Element gebündelt. Diese
|
|
Darstellungsentscheidung ändert weder Datenmodell noch Funktionsumfang.
|
|
|
|
## D-008: Widerrufbare Datenbanksitzungen und Argon2id
|
|
|
|
Sendewerk verwendet Datenbanksitzungen statt JWT. SvelteKit empfiehlt diesen
|
|
Ansatz, wenn sofortiger Widerruf wichtiger als eine zusätzliche Datenbankabfrage
|
|
ist. Ein zufälliges Sitzungstoken liegt nur als SHA-256-Hash in SQLite; der
|
|
Browser erhält es als `HttpOnly`-, `SameSite=Lax`-Cookie. Nutzerstatus und Rolle
|
|
werden bei jedem Request erneut verbunden. Lokal erlaubt eine explizite
|
|
`http:`-Origin Cookies ohne `Secure`, bei `https:` wird `Secure` gesetzt.
|
|
|
|
Passwörter hasht `@node-rs/argon2` 2.2.2 mit Argon2id und den dokumentierten
|
|
Paketparametern. Zufallstokens und SHA-256 stammen aus `node:crypto`; eigene
|
|
Kryptoprimitive werden nicht implementiert.
|
|
|
|
Quellen:
|
|
|
|
- <https://svelte.dev/docs/kit/auth>
|
|
- <https://svelte.dev/docs/kit/hooks>
|
|
- <https://github.com/napi-rs/node-rs/tree/main/packages/argon2>
|
|
|
|
## D-009: Form-Actions, geschlossenes Setup und Rollenprüfung
|
|
|
|
Alle Auth-Schreibvorgänge verwenden POST-Form-Actions beziehungsweise einen
|
|
POST-Endpunkt. SvelteKits gleichursprüngliche Formularprüfung bleibt aktiv. Der
|
|
Erstadmin erfordert ein mindestens 32 Zeichen langes Betreiber-Setup-Token und
|
|
eine transaktional geprüfte leere Nutzertabelle. Danach bleibt `/setup`
|
|
geschlossen. Barkeeper-Rechte werden in jeder Verwaltungsaktion erneut geprüft;
|
|
UI-Sichtbarkeit ist keine Sicherheitsgrenze.
|
|
|
|
Quelle: <https://svelte.dev/docs/kit/form-actions>
|
|
|
|
## D-010: Lokale Mail-Outbox als sichere Vorgabe
|
|
|
|
Der Mailmodus ist standardmäßig `local` und schreibt Einladungs-/Resetnachrichten
|
|
in einen konfigurierten persistenten Pfad. Dadurch wird lokal keine echte Mail
|
|
ungefragt versandt. Nur die explizite Auswahl `smtp` initialisiert Nodemailer;
|
|
bei SMTP sind Host und Absender verpflichtend. Outbox-Dateien sind private
|
|
Geheimnisträger und werden weder versioniert noch über eine Route ausgeliefert.
|
|
|
|
Nodemailer 10.1.7 unterstützt Node 20 oder neuer und ist MIT-0-lizenziert.
|
|
Quelle: <https://github.com/nodemailer/nodemailer>
|
|
|
|
## D-011: Form-Actions und optimistische Piece-Revisionen
|
|
|
|
Piece-Schreibvorgänge bleiben normale SvelteKit-Form-Actions und funktionieren
|
|
damit ohne clientseitiges JavaScript. `use:enhance` ergänzt den Editor um
|
|
verzögertes Autospeichern und sichtbare Zustände. Vor interner Navigation wird
|
|
eine ausstehende Änderung gespeichert; beim Verlassen der Seite greift die
|
|
Browserwarnung. Jede Mutation vergleicht eine erwartete ganzzahlige Revision in
|
|
derselben SQLite-Transaktion. Veraltete Änderungen liefern 409 und überschreiben
|
|
keine aktuelleren Inhalte oder Zuordnungen.
|
|
|
|
Quelle: <https://svelte.dev/docs/kit/form-actions>
|
|
|
|
## D-012: Markdown immer nach dem Parsen bereinigen
|
|
|
|
Marked 18.1.1 rendert die gespeicherten Markdown-Texte serverseitig. Marked
|
|
weist ausdrücklich darauf hin, dass seine HTML-Ausgabe nicht bereinigt ist.
|
|
Darum reduziert sanitize-html 2.18.1 das Ergebnis anschließend auf ausgewählte
|
|
Text-, Listen-, Code- und Linkelemente. Skripte, Bilder, Ereignisattribute,
|
|
relative Protokoll-URLs und andere Schemata werden nicht zugelassen.
|
|
|
|
Quellen:
|
|
|
|
- <https://github.com/markedjs/marked>
|
|
- <https://github.com/apostrophecms/apostrophe/tree/main/packages/sanitize-html>
|
|
|
|
## D-013: Private Rasterbilder außerhalb statischer Assets
|
|
|
|
Uploads liegen ausschließlich im konfigurierten privaten Uploadverzeichnis und
|
|
werden über zufällige interne Namen adressiert. file-type 22.2.0 erkennt den Typ
|
|
aus dem Dateiinhalt; Client-MIME und Originalname entscheiden nicht über Typ oder
|
|
Pfad. Zugelassen sind JPEG, PNG, WebP und GIF bis 5 MB. SVG und allgemeine
|
|
Dokumente bleiben ausgeschlossen. Eine authentifizierte ID-Route liefert Bilder
|
|
mit sicherheitsrelevanten Headern aus. Das Adapter-Requestlimit liegt mit 6 MB
|
|
knapp oberhalb des fachlichen Dateilimits.
|
|
|
|
Quelle: <https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload>
|
|
|
|
## D-014: Sendung als transaktionales Aggregat
|
|
|
|
Eine ganzzahlige Revision auf `shows` schützt in Etappe 4 sämtliche Metadaten,
|
|
Abschnitte, Verwendungen und Reihenfolgen gemeinsam. Jede Mutation erhöht die
|
|
Revision in derselben SQLite-Transaktion. Damit können voneinander abhängige
|
|
Listenänderungen nicht teilweise gespeichert oder durch einen veralteten
|
|
Browserstand unbemerkt überschrieben werden.
|
|
|
|
Verwendungen speichern vollständige redaktionelle Text- und Dauerkopien. Die
|
|
Original-ID bleibt Referenz, ist aber keine Live-Datenquelle. Eine interne,
|
|
eindeutige Reservesection pro Sendung erlaubt dieselben geprüften Verschiebe-
|
|
und Löschoperationen für Plan und Reserve; Dauerabfragen schließen sie aus.
|
|
|
|
## D-015: Drag-and-drop nur als zusätzliche Bedienung
|
|
|
|
Abschnitte, Verwendungen und gefilterte Quellen können über eindeutige Griffe
|
|
gezogen werden. Dieselben Ergebnisse sind mit sichtbaren
|
|
Hoch-/Runter-Schaltflächen und Zielauswahl erreichbar. Diese Bedienelemente
|
|
funktionieren mit einfachem Klick, Touch und Tastatur auch ohne Drag-and-drop
|
|
oder clientseitige Spezialgesten. Das folgt WCAG 2.2 SC 2.5.7;
|
|
Tastaturzugang allein wäre keine ausreichende Zeigeralternative. Die
|
|
zugehörigen Form-Actions werden progressiv verbessert: Ohne JavaScript bleiben
|
|
normale POSTs möglich, mit JavaScript erfolgt die Aktualisierung ohne
|
|
Dokumentreload und mit lokalem Speicherzustand.
|
|
|
|
Quellen:
|
|
|
|
- <https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements>
|
|
- <https://svelte.dev/docs/kit/form-actions>
|
|
|
|
## D-016: Live-Befehle idempotent, tatsächliche Zeiten als Intervalle
|
|
|
|
Die Live-Steuerung verwendet eine eigene Revision und zusätzlich eine pro
|
|
Bedienaktion erzeugte Befehls-ID. Die Datenbank akzeptiert jede Kombination aus
|
|
Sendung und Befehls-ID nur einmal. Ein nach Verbindungsabbruch wiederholter
|
|
Befehl liefert dieselbe Ergebnisrevision und erzeugt weder einen zweiten
|
|
Statuswechsel noch ein zweites Zeitintervall. Laufzeit wird nicht als einzelner
|
|
überschreibbarer Wert, sondern als Summe abgeschlossener und höchstens eines
|
|
aktiven Intervalls ermittelt. Dadurch bleiben Zurückgehen und Wiederaufnahme
|
|
korrekt.
|
|
|
|
## D-017: Präsentationsausgabe als Positivliste mit gehashtem Zugang
|
|
|
|
Die heinzlive-Ausgabe ist kein eingeschränkter Aufruf der internen Live-Ansicht,
|
|
sondern eine eigene Datenprojektion. Zulässig sind ausschließlich ausdrücklich
|
|
präsentierter Titel und Präsentationstext, öffentliche Quellen sowie zugehörige
|
|
Bilder und Alternativtexte. Recherche, Einleitung, Moderation, eigene Notizen,
|
|
Ablauf, Nutzer und interne Dateinamen werden nicht in HTML oder JSON übernommen.
|
|
Ein zufälliger 256-Bit-Token steht in der URL, während SQLite nur seinen
|
|
SHA-256-Hash speichert. Medienzugriff prüft Token, aktuelle Präsentation und
|
|
Bildzuordnung gemeinsam. Synchronisierung erfolgt über kleines `no-store`-
|
|
Polling statt über eine dauerhaft offene Serververbindung.
|
|
|
|
## D-018: Verwendung aus bestehenden Sendungsdaten ableiten
|
|
|
|
„Gesendet“ ist kein Zustand am Fundus-Piece. Die Anzeige wird ausschließlich
|
|
aus der vorhandenen Original-ID, einer erledigten Verwendung außerhalb der
|
|
Reservebank und dem Status `completed` ihrer Sendung abgeleitet. Dadurch können
|
|
Einplanung, Reserve und Überspringen ein Original nicht versehentlich als
|
|
gesendet markieren, und mehrere tatsächliche Verwendungen bleiben als Historie
|
|
sichtbar.
|
|
|
|
Eine Duplikation im Sendungsablauf kopiert den konkreten Verwendungssnapshot und
|
|
nicht das Original-Piece. Die neue ID, der Status `open` und fehlende
|
|
Live-Intervalle trennen sie von der bisherigen Durchführung. Der allgemeine
|
|
Verwendungseditor darf den Live-Status nicht mehr aus Formulardaten schreiben;
|
|
Statuswechsel bleiben der Live-Geschäftslogik vorbehalten.
|
|
|
|
Eine bestehende Einplanung wird nicht automatisch mit späteren Änderungen am
|
|
Original synchronisiert. Die ausdrücklich ausgelöste Aktion **Aus Fundus
|
|
aktualisieren** überschreibt nur Titel, Dauer und Redaktionstexte. Eigene
|
|
Notizen, Status und Messintervalle bleiben erhalten. Alle Zuordnungen und Filter
|
|
verwenden ausschließlich `original_piece_id`, niemals veränderliche Titel. Im
|
|
allgemeinen Fundus gilt „eingeplant“ global für jede nicht abgeschlossene
|
|
Sendung; im Sendungsbau gilt es lokal für die aktuelle Sendung samt Reserve.
|
|
|
|
## D-019: Lokale Linkmetadaten und streng abgeleitete Video-Embeds
|
|
|
|
Sendewerk verwendet Metascraper ausschließlich serverseitig, um Titel,
|
|
Beschreibung, Bild und Herausgeber einer ausdrücklich gewählten vorhandenen
|
|
Quelle zu ermitteln. Die Ergebnisse werden gespeichert und nicht bei jedem
|
|
Seitenaufruf neu geladen. Manuell korrigierte Felder haben Vorrang. Ein eigener
|
|
HTTP(S)-Client prüft Protokoll, Port, DNS-Ergebnisse und jedes Redirectziel,
|
|
bindet den Request an die geprüfte öffentliche Adresse und begrenzt Redirects,
|
|
Laufzeit und Antwortgröße. Damit ist weder ein externer Vorschaudienst noch die
|
|
Übernahme fremden HTMLs erforderlich.
|
|
|
|
Player werden nur für streng erkannte YouTube- und TikTok-URLs erzeugt. Aus der
|
|
validierten Anbieter-ID baut Sendewerk die offiziellen Embed-Adressen selbst;
|
|
gespeichertes oder fremd geliefertes Embed-HTML wird nicht verwendet. YouTube
|
|
nutzt die datenschutzreduzierte `youtube-nocookie.com`-Adresse und den
|
|
`start`-Parameter. TikTok erhält eine Startposition nach `onPlayerReady` über
|
|
die dokumentierte Player-`postMessage`-API. Autoplay bleibt ausgeschaltet, und
|
|
ein Inhaltswechsel ersetzt das Iframe vollständig.
|
|
|
|
Quellen:
|
|
|
|
- <https://github.com/microlinkhq/metascraper>
|
|
- <https://developers.google.com/youtube/player_parameters>
|
|
- <https://developers.google.com/youtube/terms/required-minimum-functionality>
|
|
- <https://developers.tiktok.com/doc/embed-player>
|
|
- <https://developers.tiktok.com/doc/embed-videos>
|
|
- <https://owasp.org/www-community/attacks/Server_Side_Request_Forgery>
|
|
|
|
## D-020: Stabile Ausgabekanal-Adresse mit Fragment-Schlüssel
|
|
|
|
OBS soll eine dauerhaft konfigurierte Adresse verwenden können, ohne dass ein
|
|
Zugangsschlüssel in HTTP-Requests, Serverlogs oder Referer-Headern erscheint.
|
|
Darum bleibt die Kanaladresse `/heinzlive/default` stabil und trägt den
|
|
zufälligen Schlüssel ausschließlich als URL-Fragment. Ein kleiner Browser-POST
|
|
tauscht ihn gegen ein `HttpOnly`-, `SameSite=Strict`-Cookie; danach greifen
|
|
Seite, Polling und erlaubte Proxys nur mit diesem Cookie zu. Für die Prüfung
|
|
liegt ein SHA-256-Hash vor. Der rohe Schlüssel muss zusätzlich in SQLite
|
|
gespeichert werden, weil die angemeldete Steuerung dieselbe stabile OBS-Adresse
|
|
nach einem Neustart erneut ausgeben können muss. Damit sind Datenbank und
|
|
Sicherungen bewusst Teil der Geheimnisgrenze. Wiederholtes Öffnen behält den
|
|
Schlüssel, eine ausdrückliche Rotation widerruft ihn sofort.
|
|
|
|
Die öffentliche Projektion folgt einer engeren Positivliste als der interne
|
|
Piece-Snapshot. Weder private Uploads noch alle Piece-Quellen werden implizit
|
|
veröffentlicht. Passieren dürfen nur öffentlicher Text, Präsentationstitel sowie
|
|
die ausdrücklich gewählte und erneut validierte Link- oder Videoquelle mit
|
|
erlaubten Vorschaufeldern.
|
|
|
|
## D-021: Vorlagen-Snapshots und idempotente Direktanlage
|
|
|
|
Persistente Piece-Vorlagen speichern validierte, tief kopierte JSON-Snapshots
|
|
mit eigener Revision. Damit bleiben sie unabhängig von späteren Änderungen am
|
|
Ausgangs-Piece, ohne für jedes optionale Piece-Feld eine zweite Tabellenstruktur
|
|
zu benötigen. Die virtuelle leere Vorlage wird nicht persistiert.
|
|
|
|
Eine Direktanlage im Sendungsplan ist ein einzelner fachlicher Befehl. Piece,
|
|
Verwendung und Sendungsrevision werden in derselben SQLite-Transaktion
|
|
geschrieben. `planning_commands` macht die Befehls-ID eindeutig und speichert
|
|
das Ergebnis, damit Wiederholungen nach Verbindungsabbrüchen keine Dubletten
|
|
erzeugen. Interner und öffentlicher Titel werden getrennt gespeichert und beim
|
|
Einplanen getrennt in den Verwendungssnapshot kopiert.
|
|
|
|
## D-022: Werkzeuge und Live-Abschnittszeit als eigene Snapshot-Grenzen
|
|
|
|
Favoriten bleiben ein boolesches Merkmal des Fundus-Originals. Sie ändern weder
|
|
Revision, Status noch Verwendungshistorie und werden nicht pro Konto
|
|
dupliziert. Der Werkzeugkatalog ist eine eigene Entität: Formatbeziehungen
|
|
beschreiben nur die Eignung, während Piece-Zuordnungen den privaten Arbeitslink
|
|
und die Notiz tragen. Katalogeinträge werden deaktiviert statt gelöscht, damit
|
|
bestehende Piece- und Sendungsbezüge lesbar bleiben.
|
|
|
|
Beim Einplanen kopieren eigene Relationstabellen alle Quellen, Werkzeuge und
|
|
sortierten Themennamen an die Verwendung. Damit können Katalognamen, URLs,
|
|
Themen und Originalzuordnungen später geändert werden, ohne eine vorbereitete
|
|
Sendung still umzuschreiben. Nur die bewusste Aktion **Aus Fundus aktualisieren**
|
|
ersetzt diese Snapshots. Die öffentliche Positivliste darf daraus ausschließlich
|
|
die Themen-Dachzeile übernehmen; private Materiallinks und Werkzeugdaten bleiben
|
|
auf der authentifizierten Live-Route.
|
|
|
|
Der Abschnitts-Countdown verwendet von den Piece-Laufintervallen getrennte
|
|
`live_section_timers` und `live_section_timer_runs`. Beim ersten tatsächlichen
|
|
Eintritt wird eine positive explizite Abschnittsdauer eingefroren. Weitere
|
|
Pieces desselben Abschnitts nutzen denselben Timer; Abschnittswechsel schließen
|
|
ihn. So bleiben Reload und Wiederaufnahme persistent, ohne die vorhandene
|
|
Piece-Zeiterfassung doppelt zu zählen.
|
|
|
|
## D-023: Ausdrückliche Medienauswahl und wiederherstellbares Entfernen
|
|
|
|
Ein privater Piece-Upload wird nicht allein durch seine Existenz öffentlich.
|
|
Das Original verweist ausdrücklich auf höchstens ein aktives
|
|
Präsentationsmedium. Beim Einplanen oder bewussten Aktualisieren wird diese
|
|
Auswahl mit Medium-ID und Alternativtext an die Verwendung kopiert. Die
|
|
öffentliche Route akzeptiert nur das Medium des aktuell präsentierten
|
|
Usage-Snapshots; Originalname, interner Speicherpfad und andere Uploads bleiben
|
|
außerhalb der Projektion.
|
|
|
|
Medien und Pieces werden nicht physisch gelöscht, solange bestehende
|
|
Sendungsfassungen auf ihre unveränderlichen Snapshots angewiesen sein können.
|
|
Zeitstempel blenden sie aus aktuellen Arbeitslisten und neuen Einplanungen aus,
|
|
erhalten aber Referenzen und ermöglichen die Wiederherstellung von Pieces. Eine
|
|
Verwendung oder ein Abschnitt darf dagegen nur entfernt werden, solange keine
|
|
betroffene Verwendung jemals live gelaufen ist und weder aktuell noch
|
|
präsentiert wird.
|
|
|
|
Der lokale Iconkatalog enthält die vollständige Classic-Regular-Familie mit
|
|
3.671 Icons. Ein generierter Metadatenkatalog ermöglicht Suche und serverseitige
|
|
Validierung, während ein statischer SVG-Sprite über den gemeinsamen
|
|
`currentColor`-Renderer dargestellt wird. So lädt die Navigation nicht sämtliche
|
|
Pfaddaten in ihren JavaScript-Chunk. Es gibt weder einen externen Laufzeitabruf
|
|
noch eine Laufzeitabhängigkeit vom benachbarten Quellverzeichnis; Herkunft und
|
|
Lizenz stehen in `THIRD_PARTY_NOTICES.md`.
|
|
|
|
## D-024: Private Live-Regie als fokussierter Viewport
|
|
|
|
Die authentifizierte Live-Steuerung nutzt einen eigenen Root-Layoutmodus und
|
|
rendert die allgemeine App-Navigation nicht. Kopf- und Aktionszeile bleiben fest;
|
|
Moderation und Ablauf sind die einzigen unabhängig scrollenden Arbeitsflächen.
|
|
Die Umschaltung zum mobilen Sheet hängt von der Fensterbreite ab, damit ein
|
|
breites, niedriges Desktopfenster weiterhin zwei Spalten verwendet. Die Höhe
|
|
steuert dort unabhängig die Dichte von Abständen und Bedienleisten. Textgröße
|
|
und Gesamtoberfläche werden nicht skaliert.
|
|
|
|
Der Kopf verwendet genau zwei kompakte Informationszeilen. Lange Kontexttitel
|
|
bleiben im Ruhezustand einzeilig und werden auf bewusste Klick- oder
|
|
Tastaturaktion lokal vollständig sichtbar. Das Regiemenü ist kein verschachteltes
|
|
`details` innerhalb der Glasfläche, sondern ein explizit gesteuertes,
|
|
viewportgebundenes Popover. So bleiben Z-Index, Maximalhöhe, Escape,
|
|
Außenklick und Fokus-Rückkehr unabhängig von Overflow und Filter-Stacking-
|
|
Contexts kontrollierbar.
|
|
|
|
Diese Darstellung ist keine neue Live-Zustandsgrenze. Zurück und die direkte
|
|
Auswahl im Ablauf sind reine lokale Betrachtungsnavigation. Sie verändern weder
|
|
aktuelles Live-Piece noch Status, Zeitintervalle, Abschnittstimer oder Ausgabe.
|
|
Während ein anderes Piece betrachtet wird, bleiben Fortschrittsaktionen
|
|
gesperrt; Präsentieren darf dagegen weiterhin ausdrücklich genau das betrachtete
|
|
Piece ausgeben. Start, Erledigen, Überspringen, Reserveeinfügung und Präsentation
|
|
bleiben serverseitige Actions mit ihren bisherigen Revisions- und
|
|
Idempotenzregeln.
|
|
|
|
## D-025: Gemeinsame Sendungsdaten für Liste und Kalender
|
|
|
|
Listenansicht, Desktop-Monatsraster und mobile Monatsagenda werden aus derselben
|
|
kompakten Sendungsliste abgeleitet. Es gibt kein zweites Kalenderdatenmodell und
|
|
keine Bibliothek für Terminverwaltung. UTC-Zeitpunkte werden mit
|
|
`Europe/Berlin` einem zivilen Tag zugeordnet; der ausgewählte Monat bleibt als
|
|
URL-Parameter navigierbar. Mehrere Sendungen eines Tages werden vollständig und
|
|
chronologisch gezeigt, Sendungen ohne Startzeit separat.
|
|
|
|
Die Präferenz `list` oder `calendar` gehört zum angemeldeten Konto und wird als
|
|
kleines geprüftes Feld an `users` gespeichert. Sie ist weder Bestandteil einer
|
|
Sendung noch Browserzustand. Der Kalender dient nur der Navigation und erhält
|
|
bewusst kein Drag-and-drop, damit eine Terminänderung ausschließlich über die
|
|
bestehende serverseitig validierte Sendungsbearbeitung erfolgt.
|
|
|
|
## D-026: Gemeinsame Live-Pause und getrennte Zeitbasen
|
|
|
|
Piece-Laufzeit, Abschnittszeit und Gesamtzeit bleiben getrennte persistente
|
|
Intervallmengen. Pause schließt in einer Live-Transaktion alle offenen
|
|
Intervalle, Fortsetzen eröffnet sie am gespeicherten Fortschritt erneut. Dadurch
|
|
zählt weder Browserwechsel noch eine beliebig lange Pause zur Sendezeit. Ein
|
|
Abschnittsreset ersetzt nur den aktiven Abschnittstimer; ein Gesamtreset löscht
|
|
nur die Gesamtlaufintervalle. Beide ändern weder Piece-Status noch Reihenfolge,
|
|
Restplanung oder Präsentationsstand.
|
|
|
|
Die Restzeit ist eine Ableitung, kein zweiter Ablaufplan. Für jeden verbleibenden
|
|
Abschnitt gilt entweder dessen positive explizite Dauer oder die Summe seiner
|
|
verbleibenden Piece-Dauern. Bereits abgeschlossene Abschnitte werden ausgelassen,
|
|
ungeplante Abschnitte werden sichtbar gezählt und eine Überziehung reduziert
|
|
nicht die geplante Zeit kommender Inhalte.
|
|
|
|
`ended_at_utc` beendet einen Durchlauf, nicht automatisch dessen offene Pieces.
|
|
Wiederaufnahme und neuer Durchlauf sind deshalb getrennte Befehle: Erstere
|
|
bewahrt Intervalle und Fortschritt, letzterer setzt beides ausdrücklich zurück.
|
|
Das private Markdownfeld „Interaktion“ folgt derselben Original-/Snapshotgrenze
|
|
wie Moderation und Überleitung, ist aber nicht Teil der öffentlichen
|
|
Präsentations-Positivliste.
|
|
|
|
## D-027: Abschlusszustand und Freigaben als eigene Sicherheitsgrenzen
|
|
|
|
Ein beendeter Live-Lauf ist wiederaufnehmbar und deshalb nicht identisch mit
|
|
einer historischen Sendung. Erst eine eigene transaktionale Finalisierung setzt
|
|
`shows.status` auf `completed`. Eine dauerhaft gespeicherte Befehls-ID verhindert
|
|
Doppelverbuchung; Usage-Historie enthält nur `done`, nie `skipped` oder Reserve.
|
|
Bestehende Usage-Snapshots bleiben Inhaltsquelle, während Abschnittskategorien
|
|
beim Abschluss zusätzlich eingefroren werden. Historische Sendungen werden nicht
|
|
erneut bearbeitet, sondern mit neuen IDs und zurückgesetztem Laufzustand kopiert.
|
|
|
|
Lesefreigaben sind keine abgeschwächten Redaktionssitzungen. Sie verwenden eine
|
|
eigene Positivprojektion mit drei kumulativen Umfängen. Roh-Tokens und
|
|
Gast-Sitzungstokens stehen nie in SQLite, optionale Passwörter verwenden Argon2id.
|
|
Passwort, Ablauf und Widerruf werden zentral vor Seite, Seitendaten und Medium
|
|
geprüft. Präsentationsmedien sind an Freigabe, Sendung und Usage-Snapshot
|
|
gebunden; beliebige Fundus-Dateien bleiben ausgeschlossen.
|
|
|
|
Freigaben verweisen auf `shows` statt ausschließlich auf Finalisierungen. Damit
|
|
kann ein Barkeeper bereits einen Entwurf oder eine laufende Planung teilen. Die
|
|
Gastprojektion liest bei jedem Aufruf die aktuellen Sendungssnapshots; der
|
|
spätere Abschluss stabilisiert genau diese bestehende Datenquelle, ohne Token
|
|
oder URL zu wechseln. Bearbeitungen behalten den Link, invalidieren aber
|
|
vorhandene Passwortsitzungen. Vollständiges Löschen ist der unmittelbare
|
|
Widerruf. Die Sendungsbearbeitung und die zentrale Verwaltung verwenden dieselbe
|
|
Geschäftslogik und bleiben Barkeepern vorbehalten.
|
|
|
|
## D-028: Additive Wartungsfelder und gemeinsamer Freigabedialog
|
|
|
|
Vorbereitungsaufwand und Twitch-Tags sind optionale additive Felder. Der neue
|
|
Bildmodus verwendet ein separates boolesches Kennzeichen, damit bestehende
|
|
Tabellen und Daten trotz älterer Modus-Check-Constraints ohne riskanten
|
|
Tabellenumbau erhalten bleiben. Freigabeverwaltung bleibt eine gemeinsame
|
|
Komponente und öffnet in allen drei internen Einstiegen als nativer Dialog.
|
|
|
|
## D-029: RSS als persistenter Eingang mit begrenztem Abruf
|
|
|
|
Externe Feeds schreiben nie direkt in den Fundus. Quelle und stabiler
|
|
Eintragsschlüssel werden in einem eigenen Eingang gespeichert; dessen Zustand
|
|
`discarded` oder `imported` wird bei einem erneuten Abruf nicht zurückgesetzt.
|
|
Erst eine bewusste Auswahl erzeugt über `content.ts` ein normales Piece samt
|
|
Originalquelle und gewählten Kategorien. Dadurch bleiben redaktionelle
|
|
Entscheidung, Dublettenschutz und Herkunft nachvollziehbar.
|
|
|
|
Manueller und geplanter Abruf verwenden denselben Adapter. Wie bei
|
|
Linkmetadaten werden private Ziele, Nichtstandardports und ungeprüfte Redirects
|
|
gesperrt; zusätzlich gelten enge Zeit-, Größen- und Mengenlimits. XML-Entities
|
|
bleiben deaktiviert und fremdes HTML wird nicht gespeichert. Ein Fehler wird am
|
|
betroffenen Feed persistiert und unterbricht keine weiteren fälligen Feeds.
|
|
|
|
## D-030: Technische Tokens, Scopes und persistente Idempotenz
|
|
|
|
Externe Assistenz erhält keine simulierte Redaktionssitzung und keine
|
|
Sonderrechte. Eigene zufällige Bearer-Tokens liegen nur gehasht vor, sind sofort
|
|
widerrufbar und erlauben entweder Lesen oder Lesen und Schreiben. REST-Routen
|
|
unter `/api/v1` verwenden die bestehenden Inhalts- und Sendungsdienste, damit
|
|
Validierung, Revisionen und Transaktionen identisch bleiben. Jede erfolgreiche
|
|
Mutation wird mit technischem Akteur, Aktion und Ziel protokolliert.
|
|
|
|
Neuanlage, Einplanung und Sortierung verlangen einen `Idempotency-Key`.
|
|
Zugang, Schlüssel, Hash des Requestinhalts und vollständige Antwort werden in
|
|
derselben SQLite-Transaktion gespeichert. Eine identische Wiederholung erhält
|
|
dieselbe Antwort; ein veränderter Request mit demselben Schlüssel wird
|
|
abgewiesen. OpenAPI 3.1 beschreibt diese Grenze maschinenlesbar. Ein MCP-Adapter
|
|
oder ein bestimmter KI-Client wird nicht vorgetäuscht: Die Anwendung bleibt ohne
|
|
KI vollständig nutzbar und eine konkrete Client-Verbindung muss separat geprüft
|
|
werden.
|
|
|
|
## D-031: Usage-basierte Vorbereitung und rotierende Freigabelinks
|
|
|
|
Die Sendungsübersicht erzeugt keinen zusätzlichen Vorbereitungszustand. Sie
|
|
projiziert konkrete Usage-Snapshots aktiver Sendungen, deren Fundus-Original noch
|
|
`preparing` ist. So bleiben bestehende Daten und Snapshots unverändert, während
|
|
ungeplante Fundus-Arbeit aus der Sendungsübersicht verschwindet.
|
|
|
|
Weil Freigabetokens nur gehasht gespeichert werden, kann ein bestehender Link
|
|
nicht erneut angezeigt werden. Ein Ersatzlink rotiert deshalb den Token derselben
|
|
Freigabe transaktional und löscht alle Gastsitzungen; Sichtumfang, Passwort und
|
|
Ablauf werden nicht stillschweigend verändert.
|
|
|
|
## D-032: Vorbereitung als Eigenschaft der konkreten Einplanung
|
|
|
|
Die erste 0.7.1-Umsetzung aus D-031 leitete den Vorbereitungszustand noch vom
|
|
veränderlichen Fundus-Original ab. Das widerspricht der bestehenden Kopierregel
|
|
für Sendungsfassungen und verhindert unterschiedliche Arbeitsstände bei
|
|
Mehrfachverwendung. D-032 ersetzt deshalb diesen Teil von D-031: Status und
|
|
Aufwand werden mit Migration `0019` additiv an `piece_usages` gespeichert.
|
|
|
|
Vorbereitende und sendefertige Originale dürfen eingeplant werden. Einplanung,
|
|
Duplikation und ausdrückliches Aktualisieren kopieren den Zustand, während der
|
|
Usage-Editor ihn danach unabhängig ändert. `piece_usages.status` bleibt davon
|
|
getrennt und beschreibt weiterhin ausschließlich den Livezustand `open`,
|
|
`current`, `done` oder `skipped`.
|