# Architektur ## Systemgrenze Sendewerk ist ein einzelner SvelteKit-Prozess. Browserseiten, serverseitige Actions, 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 +-> Mail/RSS über begrenzte 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 sieben Etappen materialisieren Identität, Inhaltsverwaltung, Sendungsplanung, Live-Durchführung, Historie, Lesefreigaben, RSS-Import und eine technische API. 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 sowie getrennte technische Zugänge | | 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. Technische Zugänge sind keine Browsersitzungen. Sie besitzen zufällige Bearer-Tokens, von denen nur SHA-256-Hashes gespeichert werden, und genau einen der Umfänge `read` oder `read_write`. Widerruf wirkt beim nächsten Request. `/api/v1` umgeht lediglich den Browser-Loginredirect; jeder Endpunkt prüft Token und erforderlichen Umfang selbst. Schreibzugriffe hinterlassen ein `audit_event`; Neuanlage und Planungsänderungen verlangen zusätzlich einen je Zugang eindeutigen, persistent gespeicherten Idempotenzschlüssel. ## RSS-Import `src/lib/server/feeds.ts` bildet Abruf, XML-Verarbeitung und Übernahme gemeinsam für manuelle Aktionen und den Prozess-Scheduler ab. Der Scheduler startet beim ersten Request und prüft minütlich höchstens zehn fällige aktive Feeds. Jeder Feed wird getrennt behandelt, sodass ein Fehler weitere Abrufe nicht stoppt. HTTP-Abrufe erlauben nur HTTP(S) auf Standardports, prüfen DNS sowie jedes Redirectziel gegen interne Adressen und begrenzen Laufzeit, Redirects und Antwortgröße. RSS/Atom wird mit deaktivierter Entity-Verarbeitung gelesen; höchstens 200 Einträge je Antwort werden berücksichtigt und fremdes HTML wird zu reinem Text reduziert. `feed_entries` ist ein eigener Eingang. `(feed_id, external_id)` verhindert Dubletten; Aktualisierungen ändern den redaktionellen Status nicht. `discarded` bleibt deshalb auch nach späteren Abrufen erhalten. Erst die bewusste Mehrfachauswahl erzeugt innerhalb einer Transaktion normale Inbox-Pieces mit Kategorien und Original-URL als Quelle und setzt den Eintrag auf `imported`. ## Technische REST-API Die Routen unter `/api/v1` bilden keine zweite Fachlogik, sondern rufen `content.ts` und `shows.ts` auf. Piece- und Sendungsrevisionen schützen weiterhin vor verlorenen Änderungen. `api_idempotency` speichert Request-Hash, Status und JSON-Antwort, sodass wiederholte Schreibbefehle dasselbe Ergebnis liefern und ein gleicher Schlüssel mit verändertem Inhalt abgewiesen wird. Die OpenAPI-3.1- Beschreibung ist unter `/api/v1/openapi.json` erreichbar. Ein MCP-Adapter oder produktspezifischer KI-Client gehört nicht zur Laufzeit und ist nicht Voraussetzung für die Bedienung. ## 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. ## Wartungsfelder 0.6.1 Migration `0017` ergänzt den optionalen Vorbereitungsaufwand am Original und kommagetrennt validierte Twitch-Tags am Abschnitt. Der Bildmodus wird wegen der bestehenden SQLite-Check-Constraints als zusätzliches Kennzeichen an Original und Usage-Snapshot gespeichert; nach außen bleibt er der eindeutige Modus `image`. Öffentlich passiert weiterhin nur das ausdrücklich gewählte Präsentationsmedium. ## Wartungskorrekturen 0.7.1 Migration `0019` ergänzt `preparation_status_snapshot` und `preparation_effort_snapshot` an `piece_usages` und übernimmt für vorhandene Einplanungen einmalig die damaligen Originalwerte. Danach ist ausschließlich die konkrete Sendungsfassung maßgeblich. Einplanen, Duplizieren und bewusstes Aktualisieren aus dem Fundus kopieren beide Werte; der Usage-Editor ändert sie unabhängig. Die Übersicht berücksichtigt vorbereitende Nicht-Reserve-Usages aktiver Sendungen, priorisiert zuerst deren Termin und führt direkt zum Editor. Ein Freigabe-Ersatzlink rotiert ausschließlich den gehashten Token derselben Freigabe. Umfang, Passwort-Hash und Ablauf bleiben erhalten, der alte Token und alle bisherigen Gastsitzungen werden in derselben Transaktion ungültig. Der neue Klartexttoken wird weiterhin nur in der Action-Antwort ausgegeben. ## Bedienkorrekturen 0.7.2 Der RSS-Auswahldialog erhält weiterhin ausschließlich `new`-Einträge aus `listFeedEntries`. Quellenfilter, Seiteneinteilung und noch nicht bestätigte Auswahl sind flüchtiger Browserzustand. Erst das Formular im erreichbaren Dialogfuß übergibt die gewählten IDs an die unveränderte transaktionale Übernahme; dadurch bleiben persistenter Dublettenschutz, Herkunft und Standardkategorien die einzige fachliche Wahrheit. Feed- und Zugangsdialoge verwenden dieselben globalen Management-Dialog- und Katalogstile wie Werkzeuge. Das Bearbeiten eines technischen Zugangs ändert nur Name und Umfang des bestehenden gehashten Tokens und wird auditiert. Die Live-Route projiziert zusätzlich nur, ob der feste Ausgabekanal getrennt, mit dieser oder mit einer anderen Sendung verbunden ist. Das Öffnen von Menü und Ausgabedetails führt selbst keine Mutation aus. ## Lesender MCP-Zugang 0.7.3 `/mcp` ist ein stateless Streamable-HTTP-Adapter über `content.ts` und `shows.ts`. Er unterstützt die aktuelle per Request deklarierte MCP-Fassung `2026-07-28` einschließlich `server/discover`, Header-/Body-Abgleich und `resultType`. Für derzeitige OpenAI-Clients bleiben die Initialisierungsfassungen `2025-11-25`, `2025-06-18` und `2025-03-26` verfügbar. Es gibt keine MCP-Sitzung und keinen unabhängigen SSE-Strom; jede Anfrage trägt erneut ihre Authentifizierung. Die fünf Werkzeuge sind ausschließlich lesend. Eigene Positivprojektionen lassen Mediennamen, interne Speicherpfade und Werkzeug-Arbeitsdaten aus. Die Detailprojektionen dürfen nach ausdrücklicher Zustimmung redaktionell private Recherche, Moderation, eigene Notizen, Quellen und Reserveinhalte enthalten. Keine MCP-Methode ruft Mutationsdienste auf. Sendewerk ist zugleich OAuth-Autorisierungs- und Ressourcenserver. Geschützte Ressourcen- und Authorization-Server-Metadaten verweisen ausschließlich auf die konfigurierte Origin. Öffentliche PKCE-Clients werden per begrenztem DCR registriert; die Autorisierung verlangt exakte Redirect-URI, `S256`, den Scope `sendewerk:read` und den exakten Ressourcenindikator `/mcp`. Codes sind fünf Minuten, Zugriffstokens eine Stunde gültig. SQLite speichert nur SHA-256- Hashes. Tokens sind direkt an Nutzer, Client, Scope und Ressource gebunden; Kontosperre und Passwortwechsel widerrufen sie, und jeder MCP-Aufruf verbindet erneut den aktuellen Nutzerzustand. DCR ist für die von OpenAI weiterhin unterstützte Kompatibilität vorhanden, aber pro Quelladresse und insgesamt begrenzt. Clientnamen sind nicht verifiziert; der Zustimmungsdialog kennzeichnet dies und zeigt die genaue Rücksprung-Domain. Öffentliche Produktion verlangt HTTPS, während HTTP nur an Loopback-Adressen zulässig bleibt. Der bestehende technische REST-Token wird weder gelesen noch weitergereicht.