# Architekturentscheidungen Stand: 10.11.2026. Arbeitstitel KantSites; Produktname offen. ## ADR 130 – Editor: lokale Sicherung statt Autosave auf dem Server (10.11.2026) **Kontext:** Der Entwicklungsplan nennt „Autosave mit sichtbarem Speicherstatus“. Gespeichert wurde nur per Knopf oder Strg+S; nach Absturz, geschlossenem Tab oder abgelaufener Sitzung war die Arbeit weg, und „Als JSON sichern“ hatte kein Gegenstück zum Einlesen. **Entscheidung:** Ungespeicherte Änderungen liegen zusätzlich im `localStorage` des Browsers (Schlüssel `kant-editor:`, je Seite und Bereich), geschrieben 0,6 s nach der letzten Änderung und beim Verlassen. Beim Öffnen bietet der Editor eine abweichende Sicherung zum Wiederherstellen an – als ein Schritt im Verlauf – oder zum Verwerfen. Nach dem Speichern verschwindet sie. Kein Speichern auf dem Server im Takt. **Folgen:** Jedes Speichern legt eine vollständige Fassung in `revision` an, und die wird nie gekürzt (ADR 009 ff.); ein Autosave alle paar Sekunden hätte die Fassungsliste entwertet und die Datenbank wachsen lassen. Das wäre erst mit zusammengefassten Entwurfsfassungen vertretbar – eine Schemaänderung, die hier nicht ansteht. Die Sicherung gilt nur für dieses Gerät und diesen Browser und ersetzt kein Speichern. Ist der Serverstand inzwischen neuer, sagt das Angebot das dazu; das Speichern danach überschreibt ihn nicht still, sondern legt eine neue Fassung über die fremde. Ist der Speicher des Browsers voll oder gesperrt, arbeitet der Editor ohne Sicherung weiter. ## ADR 129 – Bilder: Stufen 720 und 1280, Abbild für den Webserver (10.11.2026) **Kontext:** Die Komplettanalyse maß auf kant-it.de rund 250 ms bis zum ersten Byte eines Bildes gegen rund 100 ms für eine statische Datei, weil jedes Bild durch die Anwendung lief (ADR 009). Zwischen den Stufen 480 und 960 lag außerdem genau der Bedarf eines Telefons (412 px × 1,75 = 721 px); Becker kam damit mobil auf einen LCP von 2,6 s. **Entscheidung:** (1) `MediaLibrary::WIDTHS` ist `[480, 720, 960, 1280, 1600, 2400]`; eine Stufe knapp unter der eigenen Breite entfällt (`steps()`). Vorhandene Bilder bekommen die neuen Stufen über die Aufgabe „Bildgrößen ergänzen“ (`image-variants`, alle 15 Minuten, portionsweise) aus ihrer größten Variante – das Original wird nicht aufbewahrt. Bestehende Varianten und ihre Adressen bleiben. (2) Die Seite löst `props.sources` eines Bildes beim Ausliefern aus der Mediathek auf (`PageController::currentImageSources`), statt die im Dokument gespeicherte Liste zu drucken: Nachgereichte Stufen erreichen jede gespeicherte Seite ohne Dokumentmigration. (3) Hintergrundbilder nehmen auf dem Telefon die Stufe 720 statt 960; die Vorabladung löst das Bild je Gerät mit derselben Vererbung und Sichtbarkeit auf wie die Regel. (4) `MediaMirror` legt eine Variante beim ersten Abruf über PHP unter `public/media//.webp` ab (harter Verweis, sonst Kopie); eine Regel in `public/.htaccess` liefert sie danach ohne PHP aus. `var/media` bleibt die Quelle für Sicherung, Paket und Wiederherstellung; „Aufräumen“ und der Paketimport entfernen aus dem Abbild, was es dort nicht mehr gibt. **Folgen:** Keine Migration, kein Schemawechsel, die Adressen der Bilder bleiben. Das hebt den Teil von ADR 009 auf, nach dem Bilder nur über die Anwendung ausgeliefert werden: Was im Abbild liegt, war auch vorher für jeden abrufbar, der die Adresse kennt (die Auslieferung prüft keine Anmeldung). Lässt sich `public/media` nicht schreiben oder greift die Regel bei einem Hoster nicht, bleibt alles beim Weg über PHP. Der eingebaute PHP-Server **ohne** Router antwortet mit 404, sobald zu einer Kennung ein Ordner im Abbild liegt; `bin/dev-router.php` bildet die Regel nach, und die Prüfskripte nutzen ein eigenes Abbild (`MEDIA_MIRROR_DIRECTORY`). Die WebP-Qualität bleibt 82. Nachgereichte Stufen sind aus einer WebP-Variante gerechnet, nicht aus dem Original. ## ADR 128 – Lesen ist nachsichtig, Schreiben streng; Kennung statt Versionsspalte (10.11.2026) **Kontext:** Die Komplettanalyse vom 10.11.2026 fand drei Stellen, an denen ein gespeicherter Stand zur Falle wurde: Seiten wurden bei jedem Lesen voll geprüft, samt eigenem CSS – eine später verschärfte Regel ließ sie mit 500 antworten und im Editor nicht mehr öffnen; Einstellungen und Design überschrieb der zweite von zwei Admins still; Sicherungen galten als unversehrt, auch wenn Mediendateien fehlten. **Entscheidung:** (1) `DocumentValidator` parst eigenes CSS nur noch beim Schreiben. Beim Lesen bleibt die Strukturprüfung, das CSS eines Blocks fällt in `compileCss()` weg, wenn es nicht mehr gilt – wie ADR 116 es für Bereiche schon vorsah. Ein beim Lesen geprüftes Dokument wird je Anfrage über seinen Inhalt gemerkt und nicht noch einmal geprüft. (2) `SiteSettings::stamp()` und `Theme::stamp()` liefern einen Hash des gespeicherten JSON; die Formulare tragen ihn als `expected`, `save()` vergleicht in derselben Transaktion und wirft `StaleWrite`. (3) `Backup::create()` wirft, wenn eine Mediendatei nicht vollständig ankommt, und räumt den angefangenen Ordner weg; `intact()` zählt die Mediendateien; `rotate()` zählt nur unversehrte Sicherungen und entfernt beschädigte nach einer Stunde. **Folgen:** Keine Migration: Die Kennung ist ein Hash, keine Spalte. Ein Formular ohne `expected` (aus einem vor dem Update geladenen Bildschirm, aus Tests, aus dem Installer) speichert ungeprüft wie bisher. Eigene Skripte werden weiter auch beim Lesen geprüft. Eine Sicherung scheitert jetzt hörbar, wo sie vorher unvollständig gelang – bei vollem Speicher gibt es dann keine neue, aber die alten vollständigen bleiben. Nicht umgesetzt: der MySQL-Rückweg nach einem zurückgerollten Update (neu angelegte Tabellen bleiben stehen); alle Installationen laufen auf SQLite. ## ADR 127 – Plugins mit eigenem PHP-Code, wie bei WordPress (09.11.2026) **Kontext:** Umut will einen ImmoScout-Abgleich für DEEG, ausdrücklich als Plugin und nicht im Kern: „Ansonsten musst du Kern so erweitern, dass Plugins das können“, „wie WordPress“. Das hebt die Grundentscheidung aus ADR 103 auf, dass ein Plugin nur Manifest plus Twig ist; dort war sie mit dem fehlenden Bedarf und der Stabilität begründet. Eigene Daten und Arbeit im Takt gehen ohne Servercode nicht. **Entscheidung:** `"main"` im Manifest nennt eine PHP-Datei, die eine Funktion zurückgibt. `PluginRuntime` lädt sie für aktive Plugins, `PluginApi` nimmt Hooks, Aufgaben (im vorhandenen `Scheduler`), Seiten (durch `renderPage`, also mit Kopf, Fuß und Design), Twig-Daten und Verwaltungsbildschirme entgegen. Eigene Tabellen legt das Plugin in `onInstall` an, Präfix `plugin__`; dadurch braucht der Kern keine Plugin-Migrationen. Reine Twig-Plugins bleiben unverändert. **Folgen:** Plugin-Code läuft mit vollen Rechten, das steht in der Verwaltung und wird nicht als Sandkasten bezeichnet. Anders als bei WordPress gibt es bewusst keinen ZIP-Upload in der Verwaltung: Wer PHP auf den Server legt, tut das per FTP, ein gestohlenes Adminkonto wird so nicht zur Codeausführung. Ausnahmen grenzt die Runtime auf das Plugin ein, einen harten PHP-Abbruch nicht. Der Kern löst vorerst nur `sitemap.entries` aus; weitere Hooks kommen mit dem ersten Bedarf. ## ADR 126 – Unternehmensart, Standort und Logo in den strukturierten Daten (09.11.2026) **Kontext:** Bei Luximmo fiel auf, dass LocalBusiness immer der allgemeine Typ war und weder Logo noch Koordinaten kannte. **Entscheidung:** Drei Einstellungen unter „Einstellungen“: `businessType` aus einer festen Liste von LocalBusiness-Unterarten, `latitude`/`longitude` als Paar, `organisationLogoMediaId` aus der Mediathek. Das Logo ist bewusst getrennt vom sichtbaren Logo im Kopfbereich: Das kann eingebettetes SVG sein und hat dann keine eigene Adresse. Die Liste ist fest, damit kein Tippfehler einen ungültigen Typ erzeugt. **Folgen:** Keine Migration: Die Werte liegen im JSON der Einstellungen, fehlende Schlüssel fallen auf die Standardwerte, ausgegeben wird dann genau das Bisherige. Typ und Koordinaten gelten nur für ein lokales Unternehmen mit Anschrift, das Logo auch für eine Organisation. Eine Installation älter als diese Fassung ignoriert die Werte aus einem Inhaltspaket und gibt sie nicht aus. ## ADR 125 – Videos: eigene Bibliothek, Baustein und Sektionshintergrund (Dokumentschema v28, 04.11.2026) **Kontext:** Der Hero von kant-it.de soll ein Video im Hintergrund bekommen. Die Mediathek nahm nur Bilder und PDFs, die Alpaka-Videos von Hüfner und die Drews-Videos blieben deshalb draußen. Ein `