# Entscheidungen Stand der Quellenprüfung: 21. 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: - - - - npm-Metadaten für `@sveltejs/kit` 3.1.0 am 21.10.2026 ## D-002: Offizieller Node-Adapter `@sveltejs/adapter-node` erzeugt den Produktionsserver in `build/`. Gebaut wird mit `npm run build`, gestartet mit `node build`. `HOST` und `PORT` konfigurieren den Server. Der Adapter behandelt SIGTERM/SIGINT und kontrolliertes Herunter- fahren; ein eigener Server ist für Etappe 1 nicht erforderlich. Quelle: ## 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: - - - npm-Metadaten für `better-sqlite3` 13.0.3 am 21.10.2026 ## D-004: Cloudron-Paket Das Paket nutzt `manifestVersion: 2`, internen HTTP-Port 3000 und `/healthz`. `localstorage.sqlite.paths` nennt `/app/data/sendewerk.sqlite`, damit Cloudron eine konsistente Sicherung der aktiven SQLite-Datei erstellen kann. Das Startskript stellt Verzeichnisse und Besitzrechte her, migriert als Benutzer `cloudron` und ersetzt sich zuletzt per `exec` durch den Node-Prozess. Das Image ist auf die am 21.10.2026 offiziell dokumentierte datierte Node-24- Basis samt Digest gepinnt. Außer `/app/data`, `/run` und `/tmp` wird das Laufzeitdateisystem als read-only behandelt. Quellen: - - - - ## 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: ## 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: ## 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: ## 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: ## 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.