669 lines
41 KiB
Markdown
669 lines
41 KiB
Markdown
# 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.
|
||
|
||
## Navigation, Suche und Arbeitsflächen
|
||
|
||
`+layout.svelte` stellt für angemeldete Nutzer die globalen Bereiche Start,
|
||
Eingang, Pieces, Streams, Live und Post bereit. Desktop verwendet eine horizontale
|
||
Kopfzeile, Mobilgeräte eine obere Such-/Kontoleiste und eine Safe-Area-fähige
|
||
Bottom-Navigation. `UserMenu.svelte` bündelt Verwaltung, Abmeldung und die weiter
|
||
in `localStorage` gespeicherte explizite Auswahl Hell, Dunkel oder System.
|
||
|
||
Die Root-Route komponiert das Dashboard ausschließlich aus `listShows()`,
|
||
`getShow()`, `listPieces()` und den vorhandenen Verwendungskontexten. Sie besitzt
|
||
keinen eigenen persistenten Zustand. Pieces und Streams verwenden getrennte,
|
||
lokal gespeicherte Ein-/Ausklappzustände ihrer Kontextnavigation; URL-Parameter
|
||
bleiben die fachliche Quelle für lokale Suche und Filter.
|
||
|
||
`global-search.ts` ist eine serverseitige, ausschließlich hinter `requireUser`
|
||
verwendete Projektion. Sie durchsucht aktive und archivierte Pieces einschließlich
|
||
redaktioneller Langtexte sowie Metadaten und Usage-Snapshots aller Streams. Die
|
||
Route `/suche` gibt gruppierte Positivprojektionen mit ID, Titel, Status, internem
|
||
Ziel, Feldbezeichnung und begrenztem Ausschnitt aus. Gelöschte Pieces werden nicht
|
||
global angeboten. Es gibt keinen externen Index und keine neue Persistenz.
|
||
|
||
Die normale Piece-Arbeitsmenge schließt Originale mit materialisierter
|
||
Verwendungshistorie aus. Dieselben Originale werden unter `view=completed` mit
|
||
ihren verlinkten Streams angezeigt und bleiben über die bestehenden
|
||
Planungsfilter erneut einplanbar. Abgeschlossene Streams werden nicht mehr als
|
||
eigener globaler Archivbereich behandelt, sondern von `listShows()` in der
|
||
Streamübersicht gefiltert. Der technische `/archiv`-Pfad bleibt für archivierte
|
||
und gelöschte Pieces bestehen.
|
||
|
||
`StreamInspector.svelte` ist die dauerhafte rechte Projektion des Streamplans.
|
||
Seine flüchtige Auswahl referenziert Show, Abschnitt oder Usage nur über stabile
|
||
IDs. Der Standardzustand bearbeitet Show- und Twitch-Metadaten; Abschnitt und
|
||
Usage verwenden ihre bestehenden revisionsgeschützten Actions. Die zusätzliche
|
||
Action `updateUsageMetadata` liest die aktuelle Usage serverseitig und führt
|
||
nicht dargestellte Langtexte, Präsentationswerte, Medien und Werkzeuge in
|
||
`updatePieceUsage()` unverändert mit. Ein schmales Clientformular kann private
|
||
Felder daher weder versehentlich leeren noch die Geschäftsvalidierung umgehen.
|
||
|
||
Seit 0.8.1 liest `/suche` die optionalen URL-Filter `type`, `status`, `topic`
|
||
und `format` serverseitig. Themen und Formate stammen direkt aus den bestehenden
|
||
Taxonomietabellen; Piece-Filter wirken nicht auf Streams. Die Trefferprojektion
|
||
und ihre begrenzten Ausschnitte bleiben unverändert, es entsteht weder ein
|
||
Suchindex noch persistenter Filterzustand.
|
||
|
||
Die Piece-, Eingangs-, Post-, Planungs- und Live-Arbeitsflächen verwenden eine
|
||
gemeinsame Oberflächensprache. Pieces, Eingang und Post teilen die Klasse `unified-workspace`:
|
||
links Navigation oder Quellen, mittig die auswählbare Inhaltsprojektion und
|
||
rechts `content-quick-inspector` für häufige Felder und Aktionen. Die Auswahl ist
|
||
flüchtiger URL- beziehungsweise Browserzustand und erzeugt kein Fachobjekt. Im Streamplan ist der Ablauf die Hauptfläche, der Inspector die
|
||
dauerhafte rechte Metadatenspalte und die Piece-Auswahl eine bei Bedarf geöffnete
|
||
Nebenspalte. Eine Auswahl schließt diese temporäre Bibliothek. Unterhalb der
|
||
Desktopbreite sind Pieces, Ablauf und Details gleichwertige Pane-Zustände.
|
||
Revisionen, Drag-Daten und Fokusgrenzen bleiben die Interaktionsgrenze. Live ist
|
||
eine reguläre Seite der App-Shell und projiziert Ablauf, Moderationsdokument und
|
||
Details ausschließlich lesend. Die lokale Piece-Auswahl und Darstellungsoptionen
|
||
ändern weder den tatsächlichen Live-Stand noch Usage-Status, Reihenfolge, Timer
|
||
oder öffentliche Ausgabe.
|
||
|
||
Die gemeinsamen Klassen sind bewusst keine bloße Rasterabstraktion. Pieces und
|
||
Post verwenden für linke Kataloge dieselbe `fundus-navigation`-Basis wie Streams
|
||
und für rechte Projektionen `stream-inspector` zusammen mit `builder-pane`.
|
||
Dadurch bleiben Kopf, Formularrhythmus, Scrollgrenze und Aktionsfuß identisch.
|
||
Post ergänzt nur fachliche Felder und eine kompakte Inhaltswerkzeugzeile.
|
||
|
||
## 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 |
|
||
| Post | Aufzeichnungsteile, private Markdown-Quellen, revisionierte YouTube-/Clip-/Highlight-Elemente, Thumbnails und valide Exporte |
|
||
| 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.
|
||
|
||
## Postproduktion
|
||
|
||
`src/lib/server/post.ts` ist die gemeinsame Geschäftsgrenze für den Hauptbereich
|
||
`/post`. Jede Übersicht beginnt bei `shows.status = 'completed'`; Post besitzt
|
||
keine eigene Sendungsidentität und kann weder Entwürfe noch laufende Streams
|
||
nachträglich als abgeschlossen markieren. `post_items` hält gemeinsame Identität,
|
||
Typ, Workflowstatus, Herkunft, Position und Revision. Typbezogene 1:1-Tabellen
|
||
speichern YouTube-, Social-Clip- oder Twitch-Highlight-Felder.
|
||
|
||
Migration `0022_post_quick_workbench.sql` ergänzt `social_caption` an
|
||
`post_social_clips` und führt vorhandene TikTok-/Instagram-Captions und Hashtags
|
||
verlustarm in das gemeinsame Feld über. YouTube Shorts verwendet im aktuellen
|
||
Formvertrag ausschließlich `shorts_title`; TikTok und Instagram teilen die
|
||
Caption inklusive Hashtags. Die älteren Spalten bleiben für die additive
|
||
Migration bestehen, werden von der neuen Oberfläche aber nicht mehr getrennt
|
||
bearbeitet.
|
||
|
||
`post_recording_parts` beschreibt mehrere Teile einer Aufzeichnung. Medienpfade
|
||
sind redaktionelle Verweise und müssen für Resolve-Export absolut oder als
|
||
`file://` angegeben sein; Framerate und Timebase werden separat validiert.
|
||
`post_source_files` speichert Originalinhalt, privaten zufälligen Speichernamen,
|
||
SHA-256, Größe sowie ausschließlich aus tatsächlich gefundenen Zeilen abgeleitete
|
||
Transkript- und Zeitmarkenprojektionen. `(show_id, sha256)` verhindert Dubletten
|
||
innerhalb einer Sendung, ohne Dateien sendungsübergreifend zusammenzuführen.
|
||
|
||
Kontextdateien und `post_thumbnails` liegen im konfigurierten Uploadverzeichnis.
|
||
Die Routen unter `/post/[id]/source` und `/post/[id]/thumbnail` verlangen eine
|
||
Redaktionssitzung, prüfen die Zuordnung zur abgeschlossenen Sendung und liefern
|
||
private Sicherheitsheader. Originalnamen sind Metadaten und niemals Pfade. Pro
|
||
YouTube-Element kann ein Thumbnail ausdrücklich ausgewählt werden.
|
||
|
||
Exporte unter `/post/[id]/export` lesen dieselbe positiv validierte Projektion.
|
||
Resolve-XML benötigt Aufzeichnungspfad, Framerate, Timebase und einen positiven
|
||
Start-/Endbereich. SRT wird cueweise vollständig geprüft; unvollständige Daten
|
||
führen zu `EXPORT_NOT_READY` statt zu einer scheinbar gültigen Datei. Text- und
|
||
JSON-Exporte erzeugen keine externe Veröffentlichung.
|
||
|
||
`src/lib/server/post-ai.ts` enthält nur Typen für einen später konfigurierbaren
|
||
Provideradapter. Es gibt weder Netzwerkaufruf noch automatisch erzeugten Inhalt.
|
||
Importierte Quellen und manuelle Bearbeitung bleiben die vollständige
|
||
Produktfunktion.
|
||
|
||
## Oberflächenstruktur
|
||
|
||
Die Hauptnavigation umfasst Start, Eingang, Pieces, Streams, Live und Post;
|
||
Verwaltung bleibt im Benutzermenü.
|
||
Auf dem Desktop liegt sie als feste horizontale Glasleiste oberhalb der
|
||
Arbeitsfläche. Bis einschließlich `52rem` ersetzen eine kompakte obere Leiste
|
||
und die untere Hauptnavigation diesen Desktopkopf 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.
|
||
Die private Route `/live` läuft in derselben authentifizierten App-Shell wie
|
||
Streams. Ein Streamkopf zeigt Titel, Termin, Umfang und den expliziten
|
||
Nur-Lesen-Zustand. Darunter folgen auf breiten Ansichten Ablauf, Lesedokument und
|
||
Details als drei `builder-pane`-/`stream-inspector`-artige Flächen. Bis
|
||
einschließlich `72rem` werden diese Bereiche über eine gleichwertige Umschaltung
|
||
einzeln gezeigt; bis `52rem` bleiben der kompakte App-Header und die mobile
|
||
Bottomnavigation sichtbar.
|
||
|
||
`src/routes/live/+page.server.ts` bietet neben `load` ausschließlich die getrennte
|
||
Präsentationsaktion an. Das Auswählen einer Usage, Vor/Zurück, Schriftgröße,
|
||
sichtbare Zeiten und das Kopieren von Twitch-Tags bleiben lokaler
|
||
Browserzustand. Status-, Fortschritts-, Sortier- und Abschlussänderungen sind
|
||
nicht Teil dieser Route. Präsentieren ändert nur `presentation_usage_id` und
|
||
damit weder Show-/Usage-Status noch Reihenfolge. Die öffentliche `/heinzlive`-
|
||
Projektion und die serverseitigen historischen Live-Dienste bleiben erhalten.
|
||
|
||
Das gemeinsame Arbeitsraster besteht aus einer linken, über Kopf und Inhalt
|
||
reichenden Navigation sowie einem rechten Hauptbereich. Dessen freie Kopfzeile
|
||
besitzt bewusst keine Pane-Fläche; darunter beginnen Inhalt und optionaler
|
||
Inspector. `unified-workspace`, Post-Detail und Live verwenden dieselben
|
||
Spaltenwerte `13–16rem / minmax(0, 1fr) / 19–22rem` und `0.85rem` Abstand. Bei
|
||
mittleren Breiten folgt der Inspector unter dem Inhalt, mobil übernimmt die
|
||
bereichsspezifische Pane-Umschaltung.
|
||
|
||
Dialog- und Pane-Bewegungen sind reine Oberflächenzustände mit 140–170 ms Dauer.
|
||
Der Piece-Dialog animiert Öffnen, explizites Schließen und Backdrop; Disclosure-
|
||
Inhalte und mobile Pane-Wechsel blenden kurz ein. Unter
|
||
`prefers-reduced-motion: reduce` werden Animationen und Übergänge entfernt.
|
||
|
||
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 Fundus ist auf breiten Ansichten ein Raster aus Bibliotheksnavigation,
|
||
mittlerer Liste und rechter Schnellbearbeitung. Kleinere Desktopfenster ordnen
|
||
den Inspector unter der Liste an; mobil werden Navigation, Liste und Inspector
|
||
gestapelt. URL-Parameter tragen Suche, Filter und ausgewählte Piece-ID über den
|
||
Routenwechsel, `sessionStorage` ausschließlich die zugehörige flüchtige
|
||
Listenposition. Reduzierte Schnellformulare lesen nicht dargestellte Werte
|
||
serverseitig aus dem aktuellen Piece und führen sie unverändert mit.
|
||
|
||
Der eigenständige Piece-Editor ordnet denselben Formvertrag als modales
|
||
redaktionelles Arbeitsblatt mit eigener URL an. Titel, Einleitung, Moderationsstichpunkte,
|
||
Interaktion, Überleitung, private Recherche, Quellen und Werkzeuge bilden die
|
||
mittlere Schreibfläche. Status, Dauer, Einordnung, Präsentationsmodi und Medien
|
||
liegen auf breiten Ansichten rechts. Alle Werte werden weiterhin gemeinsam über
|
||
die bestehende `save`-Action und Piece-Revision gespeichert; separate Bild- und
|
||
Präsentationsactions bleiben eigene serverseitige Mutationen.
|
||
|
||
Der Eingang verwendet dieselbe Darstellung, behält aber zwei Datenarten: neue
|
||
`feed_entries` und bereits erzeugte Inbox-Pieces. Die rechte Projektion ruft für
|
||
RSS weiterhin ausschließlich Import-/Verwerfactions auf. Post beginnt ebenfalls
|
||
in einer dreiteiligen Übersicht; die vollständige Streamzeile und die rechte
|
||
Öffnen-Aktion verlinken direkt auf `/post/[id]`. Die Route ist eine normale
|
||
Anwendungsseite und übernimmt Seitenkopf, Datumsdarstellung, Feldgeometrie und
|
||
responsive Pane-Umschaltung der Streambearbeitung. Darunter stehen
|
||
Schnellnavigation, Editor/Übersicht und Inspector. Die linke Navigation
|
||
gruppiert YouTube, Social-Clips und Twitch-Highlights und ruft deren Sofortanlage
|
||
über dieselbe Post-Action auf. Ohne `item`-Queryparameter bleibt die Auswahl
|
||
leer: Die Mitte projiziert alle vorhandenen Post-Inhalte mit Kopieraktionen,
|
||
rechts stehen Streamzähler. Mit `item` zeigt dieselbe Mitte den Editor und rechts
|
||
dessen Eigenschaften. Mutations-URLs führen `item` mit, damit Upload oder
|
||
Speichern die aktuelle Auswahl nicht verlieren.
|
||
|
||
`createPostItem()` setzt `source_file_id` bereits beim Anlegen, wenn genau eine
|
||
Kontextdatei zum Stream existiert. `listPostPieceOptions(showId)` verbindet
|
||
`pieces`, `piece_usages` und `show_sections`; dadurch sind nur Original-Pieces
|
||
des geöffneten Streams zuordenbar. Editor und Inspector nutzen dieselben Pane-,
|
||
Feld- und Aktionsgrundlagen wie der Streamplan. Mobil werden Navigation,
|
||
Bearbeitung und Details als drei explizite Pane-Zustände angeboten.
|
||
|
||
Checkboxen und Radios bleiben im DOM native Formularsteuerungen. Das globale
|
||
Stylesheet blendet nur ihre sichtbaren Kästchen aus und projiziert `:checked` und
|
||
`:focus-visible` auf das umgebende Label. Damit sind ganze Flächen per Maus,
|
||
Touch und Tastatur wählbar, ohne Semantik oder Formübermittlung zu ersetzen. Der
|
||
aktive Zustand verwendet wie Post-Navigation und Post-Register den cyanfarbenen
|
||
Oberflächenakzent; Gold bleibt Primäraktionen vorbehalten.
|
||
|
||
`RichMarkdownField` erstellt je sichtbarem Langtext genau eine Tiptap-Instanz mit
|
||
StarterKit und offizieller Markdown-Erweiterung. Der Formularwert bleibt rohes
|
||
Markdown. Eine Bearbeitung aktualisiert ihn aus `getMarkdown()`, während das
|
||
bloße Initialisieren keinen Input auslöst. Weicht die erste Serialisierung vom
|
||
gespeicherten Text ab, startet das Feld in der Markdown-Quellansicht. Der Nutzer
|
||
kann diese Ansicht jederzeit erreichen; Autosave erzeugt die Editorinstanz nicht
|
||
neu.
|
||
|
||
## 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 `<Origin>/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.
|