Files
sendewerk/docs/DECISIONS.md
T
2026-10-27 14:57:17 +01:00

34 KiB

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:

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:

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:

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:

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:

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:

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:

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.

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.

D-033: Auswahlzustand flüchtig, Verwaltungslogik gemeinsam

Quellenfilter, Pagination und Mehrfachauswahl des RSS-Dialogs bleiben lokaler UI-Zustand. Persistiert wird erst durch die bestehende transaktionale Importaktion, damit Blättern oder Öffnen eines externen Links niemals redaktionelle Inhalte erzeugt. Die Verwaltung ruft weiterhin dieselben Feed- und Zugangsdienste auf; neue Dialoge ersetzen nur die dauerhaft ausgebreiteten Formulare. Ein technischer Zugang darf Name und Umfang ändern, behält dabei aber seinen ausschließlich gehasht gespeicherten Token und erzeugt ein Auditereignis.

Das Live-Menü zeigt einen rein gelesenen Verbindungsstatus des Ausgabekanals. Nur die bereits vorhandenen expliziten Verbinden-, Rotieren- und Trennenaktionen verändern öffentliche Ausgabe. Navigation zur Planung bleibt davon getrennt.

D-034: Nutzergebundener OAuth-Zugang statt Token-Passthrough

Der bestehende technische REST-Zugang kann und darf nicht für ChatGPT weitergereicht werden: Sein Klartexttoken ist nach der Erstellung nicht mehr verfügbar, und MCP verbietet Token-Passthrough. Sendewerk stellt deshalb einen eigenen OAuth-2.1-Authorization-Code-Flow für öffentliche Clients bereit. PKCE S256, exakte Redirect-URIs und der auf <Origin>/mcp gebundene Ressourcenindikator sind verpflichtend. Codes und einstündige Zugriffstokens liegen ausschließlich gehasht vor und sind direkt an ein aktives Nutzerkonto gebunden.

Die Autorisierungsserver-Metadaten bieten das von OpenAI unterstützte Dynamic Client Registration als Kompatibilitätsweg an. Die aktuelle MCP-Fassung stuft DCR zugunsten von Client ID Metadata Documents als veraltet ein. Ein allgemeiner CIMD-Abruf würde jedoch eine neue SSRF- und Cache-Sicherheitsgrenze eröffnen; für diese private Anwendung bleibt DCR deshalb eng mengen- und ratenbegrenzt. Der Zustimmungsdialog behandelt den selbst gemeldeten Clientnamen ausdrücklich als unverifiziert und zeigt dessen Rücksprung-Domain.

Der MCP-Adapter unterstützt sowohl die aktuelle stateless Fassung 2026-07-28 als auch die von OpenAI weiterhin dokumentierten Initialisierungsfassungen bis 2025-03-26. Tooldaten werden aus bestehenden Diensten positiv projiziert. Der einzige Scope sendewerk:read ist im Dialog ausdrücklich einschließlich privater redaktioneller Recherche beschrieben; E-Mail, Uploadnamen, Speicherpfade und Werkzeug-Arbeitsdaten werden nicht ausgegeben. Alle Werkzeuge sind als lesend markiert und haben keinen Pfad zu Mutationsdiensten.

Quellen, geprüft am 27. Oktober 2026:

D-035: Horizontale Fundus-Werkbank und Markdown als Editorgrenze

Die Desktopnavigation wird horizontal, damit der Fundus die gesamte Breite als drei fachlich eindeutige Spalten nutzen kann: Navigation, Liste oder Schreiben, sowie Metadaten und Präsentation. Das Öffnen eines Pieces wechselt nur den mittleren Arbeitskontext; Filter bleiben URL-Zustand, die Listenposition bleibt flüchtiger Browserzustand. Bei weniger Platz verschwindet zuerst die rechte Kontextspalte. Mobil wird nicht das Desktopgitter gestapelt, sondern zwischen kompakten, einzeln bedienbaren Ansichten gewechselt.

Tiptap ist ausschließlich eine Browser-Bearbeitungsschicht. Markdown bleibt persistentes und öffentliches API-Format, die vorhandene serverseitige Marked-/sanitize-html-Grenze bleibt unverändert. @tiptap/core, @tiptap/pm, @tiptap/starter-kit und @tiptap/markdown werden versionsgleich in 3.32.0 verwendet und sind MIT-lizenziert. Weil die Markdown-Erweiterung noch als frühe Veröffentlichung dokumentiert ist, löst Initialisierung keine Speicherung aus; abweichende Roundtrips öffnen die jederzeit erreichbare Quellansicht statt den gespeicherten Text still umzuschreiben.

Quellen, geprüft am 27. Oktober 2026:

D-036: Begriffe und Navigation sind Projektionen, keine neuen Fachmodelle

Die sichtbaren Begriffe Pieces und Streams ersetzen Fundus und Sendungen in der Bedienoberfläche. Persistierte Tabellen, Statuswerte, API-Pfade und bestehende Browserrouten werden nicht umbenannt: Eine rein visuelle Neuordnung rechtfertigt weder eine riskante Datenmigration noch einen Bruch externer Verträge.

Start ist eine aus vorhandenen Diensten abgeleitete Projektion. Die globale Suche bleibt ein synchroner serverseitiger SQLite-Leseweg ohne eigenen Index. Nur angemeldete Nutzer erreichen sie; Treffer enthalten kurze Ausschnitte statt vollständiger Dokumente. Lokale Bereichssuchen und URL-Filter bleiben davon unabhängig.

Bereits gesendete Pieces bilden keinen neuen persistenten Status. Wie bisher ist die materialisierte Verwendungshistorie die Quelle; sie bestimmt nun zusätzlich die sichtbare Gruppe Abgeschlossen. Ebenso werden die sichtbaren Streamzustände Angelegt und In Vorbereitung aus draft plus vorhandener Abschnittszahl abgeleitet. Die bestehenden Status- und Snapshotregeln bleiben damit alleinige Fachquelle.

Der rechte Usage-Editor ist auf Desktop ein nicht-modales Dialogelement. Dadurch bleiben dieselbe Komponente, Validierung und Form-Action erhalten, während der Ablauf daneben bedienbar bleibt. Mobil verwendet dieselbe Komponente weiterhin einen modalen Vollbildkontext.