228 lines
11 KiB
Markdown
228 lines
11 KiB
Markdown
# 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:
|
|
|
|
- <https://svelte.dev/docs/kit/creating-a-project>
|
|
- <https://svelte.dev/docs/kit/migrating-to-sveltekit-3>
|
|
- <https://svelte.dev/docs/cli/sv-create>
|
|
- npm-Metadaten für `@sveltejs/kit` 3.1.0 am 21.10.2026
|
|
|
|
## D-002: Offizieller Node-Adapter
|
|
|
|
`@sveltejs/adapter-node` erzeugt den Produktionsserver in `build/`. Gebaut wird
|
|
mit `npm run build`, gestartet mit `node build`. `HOST` und `PORT` konfigurieren
|
|
den Server. Der Adapter behandelt SIGTERM/SIGINT und kontrolliertes Herunter-
|
|
fahren; ein eigener Server ist für Etappe 1 nicht erforderlich.
|
|
|
|
Quelle: <https://svelte.dev/docs/kit/adapter-node>
|
|
|
|
## D-003: SQLite mit better-sqlite3
|
|
|
|
`better-sqlite3` 13.x wird als gekapselter synchroner Treiber verwendet. Das
|
|
Paket ist MIT-lizenziert und verlangt Node 22 oder neuer. Gegenüber
|
|
`node:sqlite` wird damit in Etappe 1 keine API verwendet, die in der offiziellen
|
|
Node-24-Dokumentation noch als Release Candidate gekennzeichnet ist. Die
|
|
Datenbankgrenze bleibt in `src/lib/server` gekapselt.
|
|
|
|
Migrationen sind eigene nummerierte SQL-Dateien und laufen transaktional. WAL
|
|
wird in Etappe 1 nicht aktiviert; für die kleine Redaktion ist das
|
|
Rollback-Journal zunächst ausreichend und vereinfacht die frühe
|
|
Betriebsprüfung.
|
|
|
|
Quellen:
|
|
|
|
- <https://nodejs.org/docs/latest-v24.x/api/sqlite.html>
|
|
- <https://www.sqlite.org/lang_transaction.html>
|
|
- npm-Metadaten für `better-sqlite3` 13.0.3 am 21.10.2026
|
|
|
|
## D-004: Cloudron-Paket
|
|
|
|
Das Paket nutzt `manifestVersion: 2`, internen HTTP-Port 3000 und `/healthz`.
|
|
`localstorage.sqlite.paths` nennt `/app/data/sendewerk.sqlite`, damit Cloudron
|
|
eine konsistente Sicherung der aktiven SQLite-Datei erstellen kann. Das
|
|
Startskript stellt Verzeichnisse und Besitzrechte her, migriert als Benutzer
|
|
`cloudron` und ersetzt sich zuletzt per `exec` durch den Node-Prozess.
|
|
|
|
Das Image ist auf die am 21.10.2026 offiziell dokumentierte datierte Node-24-
|
|
Basis samt Digest gepinnt. Außer `/app/data`, `/run` und `/tmp` wird das
|
|
Laufzeitdateisystem als read-only behandelt.
|
|
|
|
Quellen:
|
|
|
|
- <https://docs.cloudron.io/packaging/>
|
|
- <https://docs.cloudron.io/packaging/manifest/>
|
|
- <https://docs.cloudron.io/packaging/addons/#localstorage>
|
|
- <https://docs.cloudron.io/packaging/cheat-sheet/>
|
|
|
|
## D-005: Theme ohne serverseitiges Nutzerkonto
|
|
|
|
Etappe 1 speichert `light`, `dark` oder `system` in `localStorage`. Ein kurzes
|
|
Inline-Skript in `app.html` setzt das resultierende Theme vor dem ersten Paint
|
|
auf dem Wurzelelement. Dies vermeidet einen falschen Theme-Blitz und benötigt
|
|
vor Etappe 2 weder Cookie noch Konto. Die Einstellung bleibt bei Neuladen im
|
|
gleichen Browser erhalten.
|
|
|
|
## D-006: Etappe-1-Vorschauen statt Attrappen
|
|
|
|
Vorschauseiten lesen echte persistente SQLite-Seed-Daten, bieten aber keine
|
|
funktionslosen Speichern- oder Live-Schaltflächen. Ein sichtbarer Hinweis
|
|
grenzt Gestaltungsvorschauen von den späteren Fachfunktionen ab. Damit wird
|
|
keine noch nicht implementierte Funktion als fertig dargestellt.
|
|
|
|
## D-007: Arbeitsansicht nach verfügbarem Platz
|
|
|
|
Die feste Desktop-Seitenleiste wird bis `52rem` verwendet und dann vollständig
|
|
durch mobile Kopf- und Fußnavigation ersetzt. Der Sendungsbau schaltet bereits
|
|
bis `72rem` von zwei Spalten auf eine bedienbare Pane-Umschaltung um. Dadurch
|
|
bleibt die Desktopnavigation in schmalen Fenstern erhalten, ohne Fundus und
|
|
Ablauf zusammenzudrücken.
|
|
|
|
Der Fundus erscheint auf dem Desktop als kompakte Liste und auf kleinen
|
|
Bildschirmen als Kartenfolge. Vorschau- und Schreibschutzinformationen sind in
|
|
einem nativen, tastaturbedienbaren `details`-Element gebündelt. Diese
|
|
Darstellungsentscheidung ändert weder Datenmodell noch Funktionsumfang.
|
|
|
|
## D-008: Widerrufbare Datenbanksitzungen und Argon2id
|
|
|
|
Sendewerk verwendet Datenbanksitzungen statt JWT. SvelteKit empfiehlt diesen
|
|
Ansatz, wenn sofortiger Widerruf wichtiger als eine zusätzliche Datenbankabfrage
|
|
ist. Ein zufälliges Sitzungstoken liegt nur als SHA-256-Hash in SQLite; der
|
|
Browser erhält es als `HttpOnly`-, `SameSite=Lax`-Cookie. Nutzerstatus und Rolle
|
|
werden bei jedem Request erneut verbunden. Lokal erlaubt eine explizite
|
|
`http:`-Origin Cookies ohne `Secure`, bei `https:` wird `Secure` gesetzt.
|
|
|
|
Passwörter hasht `@node-rs/argon2` 2.2.2 mit Argon2id und den dokumentierten
|
|
Paketparametern. Zufallstokens und SHA-256 stammen aus `node:crypto`; eigene
|
|
Kryptoprimitive werden nicht implementiert.
|
|
|
|
Quellen:
|
|
|
|
- <https://svelte.dev/docs/kit/auth>
|
|
- <https://svelte.dev/docs/kit/hooks>
|
|
- <https://github.com/napi-rs/node-rs/tree/main/packages/argon2>
|
|
|
|
## D-009: Form-Actions, geschlossenes Setup und Rollenprüfung
|
|
|
|
Alle Auth-Schreibvorgänge verwenden POST-Form-Actions beziehungsweise einen
|
|
POST-Endpunkt. SvelteKits gleichursprüngliche Formularprüfung bleibt aktiv. Der
|
|
Erstadmin erfordert ein mindestens 32 Zeichen langes Betreiber-Setup-Token und
|
|
eine transaktional geprüfte leere Nutzertabelle. Danach bleibt `/setup`
|
|
geschlossen. Barkeeper-Rechte werden in jeder Verwaltungsaktion erneut geprüft;
|
|
UI-Sichtbarkeit ist keine Sicherheitsgrenze.
|
|
|
|
Quelle: <https://svelte.dev/docs/kit/form-actions>
|
|
|
|
## D-010: Lokale Mail-Outbox als sichere Vorgabe
|
|
|
|
Der Mailmodus ist standardmäßig `local` und schreibt Einladungs-/Resetnachrichten
|
|
in einen konfigurierten persistenten Pfad. Dadurch wird lokal keine echte Mail
|
|
ungefragt versandt. Nur die explizite Auswahl `smtp` initialisiert Nodemailer;
|
|
bei SMTP sind Host und Absender verpflichtend. Outbox-Dateien sind private
|
|
Geheimnisträger und werden weder versioniert noch über eine Route ausgeliefert.
|
|
|
|
Nodemailer 10.1.7 unterstützt Node 20 oder neuer und ist MIT-0-lizenziert.
|
|
Quelle: <https://github.com/nodemailer/nodemailer>
|
|
|
|
## D-011: Form-Actions und optimistische Piece-Revisionen
|
|
|
|
Piece-Schreibvorgänge bleiben normale SvelteKit-Form-Actions und funktionieren
|
|
damit ohne clientseitiges JavaScript. `use:enhance` ergänzt den Editor um
|
|
verzögertes Autospeichern und sichtbare Zustände. Vor interner Navigation wird
|
|
eine ausstehende Änderung gespeichert; beim Verlassen der Seite greift die
|
|
Browserwarnung. Jede Mutation vergleicht eine erwartete ganzzahlige Revision in
|
|
derselben SQLite-Transaktion. Veraltete Änderungen liefern 409 und überschreiben
|
|
keine aktuelleren Inhalte oder Zuordnungen.
|
|
|
|
Quelle: <https://svelte.dev/docs/kit/form-actions>
|
|
|
|
## D-012: Markdown immer nach dem Parsen bereinigen
|
|
|
|
Marked 18.1.1 rendert die gespeicherten Markdown-Texte serverseitig. Marked
|
|
weist ausdrücklich darauf hin, dass seine HTML-Ausgabe nicht bereinigt ist.
|
|
Darum reduziert sanitize-html 2.18.1 das Ergebnis anschließend auf ausgewählte
|
|
Text-, Listen-, Code- und Linkelemente. Skripte, Bilder, Ereignisattribute,
|
|
relative Protokoll-URLs und andere Schemata werden nicht zugelassen.
|
|
|
|
Quellen:
|
|
|
|
- <https://github.com/markedjs/marked>
|
|
- <https://github.com/apostrophecms/apostrophe/tree/main/packages/sanitize-html>
|
|
|
|
## D-013: Private Rasterbilder außerhalb statischer Assets
|
|
|
|
Uploads liegen ausschließlich im konfigurierten privaten Uploadverzeichnis und
|
|
werden über zufällige interne Namen adressiert. file-type 22.2.0 erkennt den Typ
|
|
aus dem Dateiinhalt; Client-MIME und Originalname entscheiden nicht über Typ oder
|
|
Pfad. Zugelassen sind JPEG, PNG, WebP und GIF bis 5 MB. SVG und allgemeine
|
|
Dokumente bleiben ausgeschlossen. Eine authentifizierte ID-Route liefert Bilder
|
|
mit sicherheitsrelevanten Headern aus. Das Adapter-Requestlimit liegt mit 6 MB
|
|
knapp oberhalb des fachlichen Dateilimits.
|
|
|
|
Quelle: <https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload>
|
|
|
|
## D-014: Sendung als transaktionales Aggregat
|
|
|
|
Eine ganzzahlige Revision auf `shows` schützt in Etappe 4 sämtliche Metadaten,
|
|
Abschnitte, Verwendungen und Reihenfolgen gemeinsam. Jede Mutation erhöht die
|
|
Revision in derselben SQLite-Transaktion. Damit können voneinander abhängige
|
|
Listenänderungen nicht teilweise gespeichert oder durch einen veralteten
|
|
Browserstand unbemerkt überschrieben werden.
|
|
|
|
Verwendungen speichern vollständige redaktionelle Text- und Dauerkopien. Die
|
|
Original-ID bleibt Referenz, ist aber keine Live-Datenquelle. Eine interne,
|
|
eindeutige Reservesection pro Sendung erlaubt dieselben geprüften Verschiebe-
|
|
und Löschoperationen für Plan und Reserve; Dauerabfragen schließen sie aus.
|
|
|
|
## D-015: Drag-and-drop nur als zusätzliche Bedienung
|
|
|
|
Abschnitte, Verwendungen und gefilterte Quellen können über eindeutige Griffe
|
|
gezogen werden. Dieselben Ergebnisse sind mit sichtbaren
|
|
Hoch-/Runter-Schaltflächen und Zielauswahl erreichbar. Diese Bedienelemente
|
|
funktionieren mit einfachem Klick, Touch und Tastatur auch ohne Drag-and-drop
|
|
oder clientseitige Spezialgesten. Das folgt WCAG 2.2 SC 2.5.7;
|
|
Tastaturzugang allein wäre keine ausreichende Zeigeralternative. Die
|
|
zugehörigen Form-Actions werden progressiv verbessert: Ohne JavaScript bleiben
|
|
normale POSTs möglich, mit JavaScript erfolgt die Aktualisierung ohne
|
|
Dokumentreload und mit lokalem Speicherzustand.
|
|
|
|
Quellen:
|
|
|
|
- <https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements>
|
|
- <https://svelte.dev/docs/kit/form-actions>
|
|
|
|
## D-016: Live-Befehle idempotent, tatsächliche Zeiten als Intervalle
|
|
|
|
Die Live-Steuerung verwendet eine eigene Revision und zusätzlich eine pro
|
|
Bedienaktion erzeugte Befehls-ID. Die Datenbank akzeptiert jede Kombination aus
|
|
Sendung und Befehls-ID nur einmal. Ein nach Verbindungsabbruch wiederholter
|
|
Befehl liefert dieselbe Ergebnisrevision und erzeugt weder einen zweiten
|
|
Statuswechsel noch ein zweites Zeitintervall. Laufzeit wird nicht als einzelner
|
|
überschreibbarer Wert, sondern als Summe abgeschlossener und höchstens eines
|
|
aktiven Intervalls ermittelt. Dadurch bleiben Zurückgehen und Wiederaufnahme
|
|
korrekt.
|
|
|
|
## D-017: Präsentationsausgabe als Positivliste mit gehashtem Zugang
|
|
|
|
Die heinzlive-Ausgabe ist kein eingeschränkter Aufruf der internen Live-Ansicht,
|
|
sondern eine eigene Datenprojektion. Zulässig sind ausschließlich ausdrücklich
|
|
präsentierter Titel und Präsentationstext, öffentliche Quellen sowie zugehörige
|
|
Bilder und Alternativtexte. Recherche, Einleitung, Moderation, eigene Notizen,
|
|
Ablauf, Nutzer und interne Dateinamen werden nicht in HTML oder JSON übernommen.
|
|
Ein zufälliger 256-Bit-Token steht in der URL, während SQLite nur seinen
|
|
SHA-256-Hash speichert. Medienzugriff prüft Token, aktuelle Präsentation und
|
|
Bildzuordnung gemeinsam. Synchronisierung erfolgt über kleines `no-store`-
|
|
Polling statt über eine dauerhaft offene Serververbindung.
|