Files
KantSites/docs/STATUS.md
T
kantarciandClaude Opus 5.5 dd5bc16413 Close the collapsible menu after choosing a link
Same-page anchors kept the details menu open. scrolled.js closes it on
any link click and loads only where a collapsible menu holds such links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:50:23 +02:00

355 KiB
Raw Blame History

Projektstatus

Fassung 1.32.1: Menü schließt nach einer Wahl (12.10.2026)

Anlass: Umut: Ein Link auf ein Sprungziel (#kontakt) im Mobilmenü ließ das Menü offen.

Änderung (ADR 119): scrolled.js schließt das aufgeklappte Menü beim Klick auf einen Link darin. Geladen wird es zusätzlich nur, wenn ein aufklappbares Menü Sprungziele derselben Seite enthält; Seiten ohne solche Links bleiben ohne Skript (Links auf andere Seiten laden neu, no-store verhindert ein offenes Menü nach „Zurück“).

Geprüft: PHPUnit 665 Tests grün, npm test 41 grün; im Browser auf Hüfner (iPhone): „Kontakt“, „Angebot anfragen“, „Leistungen“ schließen das Menü und springen. Bekannt und vorbestehend: test:responsive-navigation scheitert am Klick im Editor-Strukturbaum.

Live Hüfner (12.10.2026): Umut hat die Datenbank hochgeladen, ohne Medienordner – Bilder antworteten mit 404 (Kennungen vom letzten Neuaufbau). Lösung: var/huefner-paket.zip unter System → „Inhalte übertragen“ importieren; bringt die passenden Medien mit.

Fassung 1.32.0: Inhaltspakete – Export und Import ohne die Konten (12.10.2026)

Anlass: Umut: Nach jedem Hochladen einer neu aufgebauten Datenbank galt live ein anderes Passwort; gewünscht ist ein Importer mit der Wahl, ob Benutzer/Passwort überschrieben werden („gerne auch als Plugin“).

Neu im Kern (ADR 118): System → „Inhalte übertragen“: Paket exportieren (Download, Konten nur mit Häkchen) und importieren (Upload oder aus var/pakete/, Upload-Grenze des Hostings angezeigt). Ersetzt werden Seiten mit Fassungen, Bereiche, Menüs, Medien/PDFs, Formulare, Weiterleitungen, Kategorien, Übersetzungen, Gestaltung und Einstellungen; Anfragen (neu dem Formular zugeordnet), Statistik, Aufgaben, Plugin-Stand und Turnstile-Zugangsdaten bleiben; Konten nur mit eigenem Häkchen. Vorher Sicherung, währenddessen Wartung, jede Datei und jedes Dokument geprüft. Konsole: cms:paket:export, cms:paket:import. Im Kern statt als Plugin, weil Plugins keinen Servercode ausführen. reset-huefner.sh schreibt zuletzt var/huefner-paket.zip.

Geprüft: PHPUnit vollständig 665 grün (neu SitePackageTest: Import ersetzt Inhalte und behält Konten, Anfragen, Dienst-Zugangsdaten; Konten nur mit beiden Häkchen; neuere Fassung abgelehnt; fünf manipulierte Pakete ändern nichts – fremde Datei mit ../, Skript als Medium, veränderter Inhalt, fremde Tabelle, ungültiges Dokument). npm test 41, lint:twig, php-cs-fixer. Durchlauf auf der Konsole: Hüfner exportiert und in eine veränderte Kopie importiert (Passwort, Anfrage, Turnstile blieben, Alpakaseite kam zurück, Medienordner ersetzt). Oberfläche in der lokalen Hüfner-Verwaltung: zu großer Upload mit Hinweis abgelehnt, „Konten übernehmen“ bei Paket ohne Konten abgebrochen, Import aus var/pakete/ abgeschlossen, danach weiter angemeldet. test:editor grün inklusive axe der Systemseite – ein erster Lauf parallel zu PHPUnit scheiterte, der Wiederholungslauf allein bestand.

Veröffentlicht: Tag v1.32.0, Release-Eintrag mit Archiv (3629 Dateien, 5,3 MB, gebaut aus var/release-source-1.32.0), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt. Keine Migration.

Nächster Schritt: huefner-gartenbau.de auf 1.32.0 aktualisieren, dann var/huefner-paket.zip unter System → „Inhalte übertragen“ importieren (ohne Konten-Häkchen).

Fassung 1.31.0: Menü schließt beim Klick daneben, Netzwerk-Symbole; Hüfner-Kopf (12.10.2026)

Anlass: Umut: aufgeklapptes Mobilmenü schließt nicht beim Klick daneben; bei Hüfner wirkt der Kopf auf dem iPad verloren (Menü-Knopf mittig), und Facebook, Instagram und „Angebot anfragen“ sollen ins Menü.

Neu im Kern (ADR 117): Klick neben das aufgeklappte Menü schließt es – reines CSS (unsichtbare Fläche am Menü-Knopf), kein Skript. Facebook, Instagram und WhatsApp im Symbolsatz (Menü-Buttons, Buttons, Symbole). Die Strichattribute der Symbole stehen jetzt als svg.block-icon in site.css/admin.css statt an jedem Symbol (rund 4 KB weniger HTML je Seite).

Hüfner (nur Inhalt): Kopf aus Logo und rechtsbündigem Menü; „Angebot anfragen“, Facebook und Instagram als Menü-Buttons. Desktop zeigt in der Zeile nur „Angebot anfragen“ (F/I stehen in der Leiste darüber), im aufgeklappten Menü stehen alle drei, die Symbole nebeneinander. Menü-Knopf auf iPad und Telefon rechts; Abstand und Schrift schrumpfen knapp über 1024 px mit, damit die Zeile nicht umbricht. Upload-Paket neu (var/huefner-upload.zip, braucht 1.31.0).

Geprüft: PHPUnit vollständig 657 grün; npm test 41; lint:twig; test:menu-buttons und test:editor grün inklusive axe; test:browser auf Hüfner grün (HTML Startseite rund 148 KB). Eigene Browserprüfung auf iPad quer und iPhone: öffnen, Menülinks bedienbar, Klick daneben schließt, kein waagerechtes Scrollen; Kopf bei 810, 1025, 1080, 1100, 1280 und 1366 px im Bild. Veröffentlicht: Tag v1.31.0, Release-Eintrag mit Archiv (3624 Dateien, 5,3 MB, gebaut aus var/release-source-1.31.0), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt.

Nächster Schritt: huefner-gartenbau.de auf 1.31.0 aktualisieren, dann var/huefner-upload.zip einspielen (Reihenfolge wichtig: die Datenbank nutzt die neuen Symbole).

Bekannt: test:responsive-navigation scheitert im Editorteil am Klick auf den Strukturbaum – auch ohne diese Änderungen (Gegenprobe), vorbestehend.

Fassung 1.30.0: Härtung nach dem Sicherheitscheck (12.10.2026)

Anlass: Umut: „Sicherheitscheck für KantSites … das Ding muss bombensicher sein.“ Prüfung in fünf Bereichen plus Blick von außen auf huefner-gartenbau.de und kant-it.de; Ergebnis im Chat vom 12.10.2026, Umsetzung „1.30.0, Signatur später“.

Behoben (ADR 116): Installationsschlüssel für den Installer (var/install.key, von install.php angelegt und angezeigt); Sperrdateien im Projektstamm und in allen internen Ordnern (var/ legt die Website selbst an); verknüpfte Sektionen nach ihrem eigenen Schema-Stand gerendert (kein ungefiltertes HTML durch alte Sektionen); Redakteure ändern Adresse, Papierkorb, Bereiche und Beitragsdaten veröffentlichter Seiten nicht mehr; geplante Veröffentlichung mit fester Fassung (Migration 20261012000100); Formular-Siegel je Formular, IPv6 je /64, Obergrenze je Formular und gesamt mit sichtbarem Hinweis; offene Weiterleitung per Tabulator beim Einwilligen; Update-Ziel nur 1.2.3; eigenes CSS bleibt im Block.

Geprüft: PHPUnit vollständig 657 Tests: im Gesamtlauf 656 grün, ein Fehlschlag (ArchiveUpdateTest: die neue .htaccess im Projektstamm stand weder in der Ersetzungsliste noch in den bewussten Ausnahmen – jetzt als Ausnahme eingetragen, weil sie dem Betreiber gehören kann), danach die betroffenen Dateien (ArchiveUpdate, Cms, Installation, InstallFlow, SingleFileInstaller, Translation; 436 Tests) erneut grün. Neu getestet: Installer nur mit Schlüssel (falscher Schlüssel, gefälschtes Cookie, Verweis aus install.php, Löschen nach dem ersten Konto), Sperrdateien, Sektion mit altem Schema, Redakteur vs. veröffentlichte Seite, geplante Fassung, Formular-Siegel je Formular, IPv6-/64, Flut-Grenze mit Hinweis, Einwilligungs-Rücksprung mit Tabulator/Umbruch/Backslash, CSS-Geschwister/Kommentare. Gegenprobe: der Sektionstest schlägt ohne den Fix an. npm test 41, lint:twig, php-cs-fixer; test:browser auf Hüfner grün; test:editor grün inklusive axe; im Browser ein echtes Absenden des Hüfner-Formulars mit neuem Siegel (Testeinsendung danach gelöscht). Hüfner-Upload-Paket mit migrierter Datenbank (20261012000100) neu gebaut.

Veröffentlicht: Tag v1.30.0, Release-Eintrag mit Archiv (5,5 MB, gebaut aus var/release-source-1.30.0, enthält alle Sperrdateien inkl. vendor/.htaccess), .sha256 und install.php (mit Installationsschlüssel); Prüfsumme nach dem Herunterladen bestätigt. Eine Migration (20261012000100), läuft mit dem Updater.

Nächster Schritt: Installationen auf 1.30.0 aktualisieren (huefner-gartenbau.de, kant-it.de, weitere); bei Hüfner danach das Upload-Paket einspielen und die HTTPS-Zeilen einschalten.

Offen (niedrig): signierte Releases; Login-Antwortzeit für unbekannte Benutzer; Trusted-Proxy-Einstellung für Hosting hinter CDN; Turnstile-Hostname; %PDF- ab Byte 0; archivierte PDFs bleiben abrufbar; Statistik ohne Obergrenze für erfundene Verweise; MySQL-Passwort in der Prozessliste bei Sicherungen; Theme-Vorlagen ohne Versionsprüfung. Live: huefner-gartenbau.de antwortet auch über http:// ohne Umleitung und ohne HSTS – die HTTPS-Zeilen in public/.htaccess einschalten.

Fassung 1.29.0: Einwilligung ohne Neuladen (12.10.2026)

Anlass: Umut auf der Hüfner-Seite: Beim Einwilligen ins YouTube-Video lädt die Seite komplett neu.

Neu im Kern (ADR 115): public/assets/consent.js schickt Hinweis und Knopf am eingebetteten Inhalt im Hintergrund ab (X-Kant-Consent: 1 → 204 mit Cookie statt Weiterleitung), holt die Seite neu und tauscht nur Einbettungen und „Rechtliches“ aus; Fokus an den Rahmen bzw. an den Seitenanfang. Ohne JavaScript und bei Fehlern bleibt das Formular mit Neuladen. frame-src nennt die Hosts aller Einbettungen der Seite auch vor der Einwilligung (die Adresse des Inhalts weiterhin erst danach im HTML). Das Skript lädt nur, solange eine Einwilligung offen ist; test:browser zählt es nicht zum Budget von drei Skripten.

Geprüft: PHPUnit vollständig 644 grün (neu: 204-Antwort mit Cookie, kein Cookie von fremder Herkunft, Weiterleitung ohne Kopfzeile; angepasst: Skript bei offener Frage, script-src 'none' nach der Entscheidung, frame-src vor der Einwilligung), npm test 41, lint:twig, php-cs-fixer. Im Browser auf der Hüfner-Kopie: Knopf am Video, „Nur notwendige“ und „Alle akzeptieren“ ohne Neuladen, Fokus aufs Video, ohne JavaScript weiterhin mit Neuladen, keine Konsolenfehler. test:browser grün, Lighthouse Produktion Startseite mobil 96, Desktop 98, sonst 100. test:editor nicht gelaufen (Editor unverändert, Skript dort nicht eingebunden).

Veröffentlicht: Tag v1.29.0, Release-Eintrag mit Archiv (3611 Dateien, 5,3 MB, gebaut aus var/release-source-1.29.0), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt. Keine Migration.

Nächster Schritt: Installationen mit Einwilligungshinweis (huefner-gartenbau.de, Immobilien DEEG) auf 1.29.0 aktualisieren.

Fassung 1.28.1: weißer 500er bei IONOS durch die Umschreibregel (12.10.2026)

Anlass: huefner-gartenbau.de bei IONOS zeigte nach der Einrichtung auf jeder Adresse einen weißen 500er.

Ursache und Änderung (ADR 114): PHP 8.4.26 lief (test.php), /index.php/install lieferte 200, nur umgeschriebene Adressen scheiterten – Umschreibschleife in public/.htaccess. Neu im Kern: RewriteBase / und RewriteRule ^index\.php$ - [L] vor der !-f-Regel; Test in CmsTest::testEveryPublicAnswerCarriesItsProtectiveHeaders.

Geprüft: Umut hat beide Zeilen auf IONOS eingetragen, danach lädt /install; auch mit eingeschalteter Options-Zeile. PHPUnit vollständig: 643 Tests grün, eine Warnung aus UpdaterTest (fehlende .env im Testordner, unabhängig von dieser Änderung); php-cs-fixer sauber.

Veröffentlicht: Tag v1.28.1, Release-Eintrag mit Archiv (3610 Dateien, 5,3 MB, gebaut aus var/release-source-1.28.1), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt. Keine Migration.

Offen: Der Installer auf huefner-gartenbau.de war bis zum Abschluss der Einrichtung öffentlich erreichbar.

Nächster Schritt: Bestehende IONOS-Installationen auf 1.28.1 aktualisieren.

Hüfner: Änderungen aus dem Kundentermin (12.10.2026)

Anlass: Umuts handschriftliche Notizen vom Hüfner-Termin plus Antworten auf Rückfragen. Nur Inhalt und Seed-Skript, kein Kerncode.

Erledigt (scripts/seed-huefner.php, build-huefner-assets.php, reset-huefner.sh): Alle Grüntöne auf das Baumgrün des Logos (#067F30) gedreht, „Premium“ und Hervorhebungen in Baumgrün, kleine Schrift auf Dunkel weiß statt hellgrün (Kontrast). Das „💯“ als Umuts Emoji-Bild überall, wo „100“ steht, im Text (<mark>100</mark>: die Zahl bleibt lesbar für Vorleser und KI, sichtbar ist das Bild in Schriftgröße, geschütztes Leerzeichen vor „%“); Quelle 100-emoji.png in den Unterlagen. „Miteinander. 💯 %.“-Abzeichen jetzt dunkel statt rot. Freistellungsbescheinigungen (§ 13b UStG, § 48b EStG) als PDF bei „Planung & Angebot“ und im Fuß, PQ-Bescheinigung unter „Über uns“ und im Fuß, Galabau-Signet und PQ-Logo. Imagevideo (YouTube, youtube-nocookie) als „Externer Inhalt“ – dafür jetzt Einwilligungshinweis (Ecke) und angepasste Datenschutzerklärung. Google-Bewertungen unter „100 % Qualität. 100 % Zufriedenheit.“ mit 4,9 / über 150 als Beleg, drei echte Rezensionen (gekürzt, Namen abgekürzt), Knopf zum Eintrag. 48-h-Angebotsgarantie in Ablauf und Kontakt, „Leistung“ aus dem Formular, Chips verlinken zum Formular, WhatsApp (0176 30387990) in Kontakt und Karriere, 25 Jahre / 7.000+ Projekte, „Bagger bis 15 t“ raus. Karriere: Facharbeiter und LKW-Fahrer C/CE (wie Instagram) als zwei gleich hohe, aufklappbare Karten (details/summary, SVG-Zeichnungen), „Ein Anruf genügt!“. Neue Seite /alpakas mit Umuts Text, Ergänzungen, Galerie und Plakat; „Alpakas“ im Menü, „Ablauf“ aus dem Menü raus. browser-check.mjs: PDFs zählen beim „erster interner Link“-Klick nicht mit.

Geprüft: test:browser auf 8102 für /, /alpakas, /datenschutz, /impressum grün (HTML Startseite 148.905 B von 150.000 – knapp). Lighthouse Produktion (reports/lighthouse-huefner-termin): / mobil 96, Desktop 99; /alpakas mobil 95, Desktop 100; Barrierefreiheit, Best Practices, SEO, Agentic Browsing überall 100. Im Browser: Video lädt erst nach Einwilligung, Stellenkarten klappen auf, Telefonbreite ohne waagrechtes Scrollen.

Bekannt / offen: Die Freistellungsbescheinigung § 13b gilt nur bis 31.12.2026 – dann neue Datei in die Unterlagen und neu aufbauen. Die Alpaka-Videos (mp4) sind nicht eingebunden (keine Videoablage im Kern). Offen von Umut: Logos der Sponsoren, „Über uns – mehr lesen“, ggf. Stellenbilder aus ChatGPT statt der SVG-Zeichnungen, genauer Verbandsname. Die Sprachtest-Kopie (var/browser-tests/lang) teilt var/huefner-media und hat nach dem Neuaufbau keine passenden Bilder mehr. Sicherung vorher: var/backups/huefner-vor-termin-1210/. Nebenbefund Kern: CustomCode::parse lehnt url('…') mit Anführungszeichen immer ab (optionales Anführungszeichen im regulären Ausdruck backtrackt).

Nächster Schritt: Umuts Durchsicht; fehlende Punkte nachliefern lassen.

Fassung 1.28.0: Notfallseite, Fehlerprotokoll und Update mit einem Klick (09.10.2026)

Anlass: kantarci-reisen.de zeigt nach einer per FTP ersetzten Datenbank auf jeder Adresse eine leere 500-Seite. Umut: „Fang mit 1 und 2 an und verbessere den Update-Prozess … mach es wie bei reiseSoft.“

Erledigt (ADR 113): (1) Wartungsseite mit 503 statt leerem 500er, auch wenn Symfony nicht startet; (2) Fehlerprotokoll var/log/notfall.log, per FTP lesbar; (3) Update-Karte auf der Systemseite: sucht beim Öffnen selbst, zeigt die Release-Hinweise, ein Klick mit Bestätigung, Fortschritt in fünf Schritten live, Migration im selben Vorgang, bei Fehler automatisch zurück auf die alte Fassung samt Datenbank.

Geprüft: PHPUnit vollständig 643 grün (neu: NotfallTest, Fortschrittsdatei und vollständiges Zurückschalten in ArchiveUpdateTest, Endpunkte nur für Administratoren in SystemScreenTest), php-cs-fixer. Im Browser mit einer Installation aus dem Releasearchiv 1.27.1: erfolgreiches Update in einem Klick, Zurückschalten bei einer kaputten Testfassung.

Veröffentlicht: Tag v1.28.0, Release-Eintrag mit Archiv (3610 Dateien, 5,3 MB, gebaut aus var/release-source-1.28.0), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt. Keine Migration.

Bekannt: Der neue Ablauf greift erst bei Updates, die von 1.28.0 ausgehen. kantarci-reisen.de ist noch offline (Ursache unbekannt, Datenbank lokal mit 1.27.0 lauffähig).

Nächster Schritt: kantarci-reisen.de wiederherstellen (Notfallschicht hochladen → Protokoll lesen, oder Sicherung zurück) und aktualisieren.

Fassung 1.27.1: Update in einem Zug, Kantarci Reisen auf Türkisch (09.10.2026)

Anlass: Umut: Update auf kantarci-reisen.de gab direkt danach einen 500er, nach „zurück“ ließ sich migrieren. „Können wir den Updater besser machen, dass alles automatisch abläuft, wie bei WordPress?“ Und: „Die DB hier komplett mit Übersetzung auf Türkisch fertig machen.“

Updater (ADR 112): Ein Klick spielt ein, die Anfrage endet nach dem Dateitausch mit einer schlichten Seite, die den Abschluss (Migrationen, Wartung aus) selbst aufruft – kein 500er, kein zweiter Knopf. Wirkt ab Updates, die von 1.27.1 ausgehen; 1.27.0 → 1.27.1 läuft noch den alten Weg.

Türkisch im Kern vervollständigt: feste Vorlagentexte und Formularmeldungen komplett auf Türkisch (vorher Teile auf Englisch). Zwei Sprachwahlen auf einer Seite heißen jetzt verschieden („Sprache“ im Kopf, „Sprache wählen“ sonst) – axe meldete sonst landmark-unique.

Kantarci Reisen (nur Inhalt): scripts/translate-kantarci-reisen.php schaltet Türkisch ein und übersetzt Startseite (67 Texte), Künye (/tr/kunye), Gizlilik (/tr/gizlilik), 404, Kopf, Fuß, Menü und das Anfrageformular; Anrede „siz“, Rechtstexte mit dem Hinweis, dass die deutsche Fassung gilt. Sprachwahl DE · TR im Kopf (auf dem Telefon ausgeblendet, kein Platz) und „Deutsch · Türkçe“ im Fuß. reset-kantarci-reisen.sh ruft das Skript am Ende auf. Ausgeführt auf var/kantarci-reisen.db (Sicherung var/backups/vor-1.27.0/kantarci-reisen-vor-tr.db).

Korrigiert: Die DM-Sans-600-Dateien von heute Nachmittag waren doch nötig – FontLibrary::missing() verlangt je Stärke eine Datei, sonst gibt die Website ihre eigenen Schriften ganz auf. Neu geholt (cms:fonts:fetch) und diesmal mit fonts.css committet. Live auf kantarci-reisen.de waren sie nie weg.

Geprüft: PHPUnit vollständig 637 grün (neu: Abschluss per Einmalschlüssel, Rolle, falscher Schlüssel, Neuladen), npm test, lint:twig, php-cs-fixer; Update-Ende-zu-Ende in einer Archivinstallation; test:browser auf Reisen (8104) grün; Lighthouse Produktion (reports/lighthouse-kantarci-reisen-tr): /tr, /tr/gizlilik, / überall 100, nur Leistung mobil 99 auf /tr und /. Telefonbreite ohne waagrechtes Scrollen.

Veröffentlicht: Tag v1.27.1, Release-Eintrag mit Archiv (3607 Dateien, 5,3 MB, gebaut aus var/release-source-1.27.1), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt. Die lokalen Installationen laufen aus diesem Repository und haben 1.27.1 (keine Migration).

Für den Livegang der türkischen Fassung: erst kantarci-reisen.de auf 1.27.1 aktualisieren (sonst fehlen Teile der festen türkischen Texte), dann var/kantarci-reisen.db hochladen. Achtung: die hochgeladene Datei ersetzt die Datenbank dort – Einsendungen und Änderungen, die es nur live gibt, gehen dabei verloren.

Fassung 1.27.0: Mehrsprachigkeit und übersichtlichere Formulare (09.10.2026)

Inhalt: Mehrsprachigkeit in drei Stufen (ADR 111, Schema v27, Migration 20261009000100) und die neue Formularverwaltung – Einzelheiten in den drei Einträgen darunter.

Geprüft vor dem Release: PHPUnit vollständig auf dem Release-Stand 636 grün, npm test 41, lint:twig, php-cs-fixer, test:admin --admin-only und test:editor (mit axe) grün. Lighthouse im Produktionsmodus auf der Hüfner-Testkopie mit Englisch (reports/lighthouse-sprachen): /en mobil 97/100/100/100/100, Desktop 99/100/100/100/100; / mobil 96, Desktop 99, sonst 100.

Veröffentlicht: Tag v1.27.0, Release-Eintrag auf git.kant-it.de mit Archiv (3605 Dateien, 5,2 MB, gebaut aus var/release-source-1.27.0), .sha256 und install.php; Prüfsumme nach dem Herunterladen bestätigt.

Aufgeräumt: Zwei nicht eingebundene DM-Sans-600-Dateien aus der Reisen-Installation gelöscht (die variable 400er-Datei deckt 400–700 ab), die nur umsortierte fonts.css auf den committeten Stand zurückgesetzt.

Nächster Schritt: Installationen auf 1.27.0 aktualisieren (Migration läuft über den Updater mit). Wer eine zweite Sprache will: Einstellungen → Sprachen, Sprachwahl in den Kopfbereich.

Formularverwaltung übersichtlicher (09.10.2026)

Anlass: Umut: Formulare „sehr unübersichtlich … kompakter, nicht so wuchtig und so voll“; bei mehreren Formularen erst eine Übersicht ohne Vorschau, dann in die Seite rein.

Neu: /admin/forms ist eine Übersicht – je Formular eine Zeile mit Feldern, Empfänger, Adresse, übersetzten Sprachen und Zahl der Einsendungen, darunter die Einsendungen. Bearbeitet wird auf /admin/forms/<adresse> (neu: /admin/forms/neu), nach dem Speichern bleibt man dort. Im Editor ist jedes Feld eine Zeile (Beschriftung, Typ, Pflicht), alles Weitere unter „Details“ mit einer Kurzinfo darunter (technischer Name, Optionen, Hilfetext, Bedingung, „EN ✓“). Zustellung, Aussehen und je Sprache die Übersetzung sind zuklappbar mit Zusammenfassung; Erklärtexte gekürzt. Ohne JavaScript bleiben alle Felder sichtbar und bedienbar. Beim Testformular der Hüfner-Kopie: Seitenlänge von 4.913 px auf 1.274 px.

Robuster: Ohne weitere Sprache fasst der Kern die Tabelle page_translation nicht an – Speichern und Veröffentlichen funktionieren so auch, wenn nach einem Update die Migration noch aussteht (gefunden über die Pilot-Fixture, die ohne Migrationen kopiert).

Geprüft: PHPUnit vollständig 636 grün (vor dem Umbau), danach die betroffenen 59 (Editor-Sicherheit, Formulare, Übersetzung) grün; npm test, lint:twig, php-cs-fixer; test:admin --admin-only und test:editor grün inklusive axe auf allen Verwaltungsseiten und im Formulareditor; im Browser Übersicht, Bearbeiten, Speichern, Neu, 404 für unbekannte Adresse, Telefonbreite ohne waagrechtes Scrollen.

Bekannt: test:admin ohne --admin-only scheitert im Slider-Teil der Pilotseite – ebenso auf dem unveränderten Stand 1.26.0 (eigens in einem separaten Arbeitsverzeichnis geprüft), also kein Folgefehler dieser Änderungen.

Mehrsprachigkeit, Stufe 3: Formulare, Beiträge, Einwilligung, 404 (09.10.2026)

Neu (ADR 111, keine Migration): Formulare je Sprache übersetzbar (in der Formularverwaltung am Feld: Beschriftung, Hilfetext, Optionen, Einheit; am Formular Button und Erfolgsmeldung), Fehlermeldungen und Schrittnavigation in der Sprache, Rückweg nach dem Absenden auf die übersetzte Seite; Einsendungen und E-Mail bleiben deutsch. Beitragslisten und Archive (/en/beitraege, /en/kategorie/…) mit übersetzten Titeln; Kategorie-/Schlagwortnamen und Einwilligungstext unter Einstellungen → Sprachen. 404 unter /en/… auf Englisch.

Geprüft: PHPUnit vollständig 636 grün (neu: Formular in beiden Sprachen inkl. Absenden/Fehler/Weiterleitung; Beiträge, Archive, Kategorienamen, Einwilligung, 404), npm test 41, lint:twig, php-cs-fixer. Browser (Testkopie Hüfner): Formularübersetzung gespeichert, /en zeigt „Your name (EN) * (required)“ und „Send“, / unverändert. Nicht geprüft: Formularversand per E-Mail, Lighthouse/axe auf /en.

Bekannt: Feed, /einwilligung, Datumsformat und Archiv-Präfixe bleiben deutsch.

Mehrsprachigkeit, Stufe 2: Kopf, Fuß, Menüs, feste Texte (09.10.2026)

Anlass: Umut: „Bilder lassen wir, mach weiter.“ Bilder bleiben für alle Sprachen gleich.

Neu (ADR 111, keine weitere Migration):

  • Kopf, Fuß und Sektionen übersetzen: im Bereichs-Editor derselbe Umschalter DE | EN, die Liste zeigt nur die Texte (keine Adresse, kein Seitentitel). Gespeichert gilt sofort.
  • Menüs: je Eintrag ein Feld „Beschriftung Englisch“ (leer = deutsch). Auf /en/… zeigen die Einträge ihre englische Beschriftung und führen auf die englischen Seiten.
  • Feste Texte wie „Zum Inhalt springen“, „Menü“, „Hauptnavigation“, „Impressum/Datenschutz“ in der Rechtszeile, Einwilligungsknöpfe, „Weiterlesen“, „Nach oben“ erscheinen auf englischen Seiten englisch (Französisch/Türkisch teilweise, sonst Englisch).

Geprüft: PHPUnit vollständig 634 grün, darunter neu ein Test für Kopfbereich, Menü und feste Texte in beiden Sprachen; npm test 41 grün; lint:twig; php-cs-fixer. Im Browser (Testkopie Hüfner, Port 8111): Menü mit „Services“/„References“ gespeichert, /en zeigt sie mit Zielen /en#…, „Skip to content“, „Main navigation“; Kopfbereich im Editor auf Englisch, „Request a quote“ in Vorschau und auf /en, / weiter „Angebot anfragen“. Nicht geprüft: Lighthouse/axe auf /en.

Bekannt: Formulare, Beiträge/Archive, Einwilligungstext aus den Einstellungen und die 404-Seite noch einsprachig. Nach einem Kern-Update cached die App-Vorschau alte Editor-Skripte (gleiche Fassungsnummer) – erst nach Neuladen der Assets sieht man Änderungen.

Nächster Schritt: Stufe 3 – Formulare (Felder, Meldungen, Bestätigungsmail) und Beiträge mit Archiven übersetzbar machen; danach Lighthouse/axe einer /en-Seite.

Mehrsprachigkeit, Stufe 1: Seiten übersetzen statt neu bauen (09.10.2026)

Anlass: Umut: weitere Sprache hinzufügen und zu jedem Feld die 2. Sprache eintragen, ohne die Seite für die 2. Sprache neu zu bauen; Adressen mit /en/-Präfix und übersetzten Slugs.

Neu (Kern, Schema v27, Migration 20261009000100, ADR 111):

  • Einstellungen → Sprachen: Hauptsprache bleibt Deutsch, weitere Sprachen per Häkchen (10 zur Wahl, höchstens 5). Ohne Häkchen ist nirgends etwas zu sehen.
  • Editor: Umschalter DE | EN oben. In EN zeigt die Seitenleiste alle Texte der Seite mit Original, Zustand (fehlt / übersetzt / Original geändert), Filter, „Original übernehmen“, „Übersetzung passt“; dazu Adresse und Suchtitel. Vorschau in der Sprache, Klick springt zum Text. Aufbau und Gestaltung sind in EN gesperrt. Undo, Speichern und Veröffentlichen wie gewohnt – für alle Sprachen gemeinsam.
  • Website: /en und /en/<übersetzte-adresse>, lang="en", Canonical, hreflang + x-default, og:locale, Sitemap. Links auf übersetzte Seiten führen zur Übersetzung. Nicht übersetzte Seiten: 404 unter /en/…. Neuer Baustein „Sprachwahl“ (Kürzel oder Namen).

Geprüft: PHPUnit vollständig (633, ein Fehler durch die Änderung gefunden und behoben, danach die betroffenen 426 Tests erneut grün), neu tests/TranslationTest.php (Überlagerung, Prüfung wie Hauptsprache, Adressen beim Veröffentlichen/Verschieben mit Weiterleitung, Kollisionen, HTTP mit hreflang/Links/Sitemap/404). npm test 41 grün, neu tests/translate.test.mjs (Fingerabdruck gleich PHP, Feldliste gleich Server, Undo). lint:twig, php-cs-fixer. Im Browser auf einer Kopie der Hüfner-Datenbank (var/browser-tests/lang/, Startkonfiguration sprachen-test, Port 8111): Sprache eingeschaltet, Startseite übersetzt, Undo/Redo, Speichern, Veröffentlichen, /en mit englischem Titel und hreflang, /en/impressum 404, Sitemap mit /en. Nicht geprüft: Lighthouse und axe einer /en-Seite und des Bausteins „Sprachwahl“, Tastaturfokus beim Klick in die Vorschau (das Browserfenster der Prüfung hatte keinen Fokus).

Bekannt (Stufe 2): Kopf/Fuß, Menübeschriftungen, Formulare, Beiträge/Archive, Einwilligung, 404 und feste Vorlagentexte bleiben auf englischen Seiten deutsch. Eigenes HTML und Plugin-Bausteine unübersetzbar, Bilder nicht je Sprache. Ändert der Server beim Speichern formatierten Text der Hauptsprache, können Übersetzungen fälschlich als „Original geändert“ erscheinen. Dokumente mit Übersetzungen oder Sprachwahl brauchen einen Kern mit Schema v27.

Nächster Schritt: Stufe 2 – Menüs und Kopf-/Fußbereiche übersetzen, danach Formulare und Beiträge. Vorher mit Umut klären, ob Bilder je Sprache austauschbar sein sollen.

Zehnte Installation „Kantarci Reisen“ (08.10.2026)

Anlass: Umut: neue Website nach dem Mockup Downloads/Kantarci-Reisen-Komplettpaket.zip, DB und Medienordner nach gleichem Muster, Inhalte dürfen individualisiert werden. Inhaber I. Kantarci, Ooser Hauptstraße 51, 76532 Baden-Baden, info@kantarci-reisen.de. Für reiseSoft ein echter Screenshot aus Downloads/reiseSoft, Plattform unter reisesoft.kantarci-reisen.de.

Neu (nur Inhalt, kein Kerncode): Datenbank var/kantarci-reisen.db, Medien var/kantarci-reisen-media, Bildquellen var/kantarci-reisen-assets, Startkonfigurationen kantarci-reisen (Port 8104, dev) und kantarci-reisen-prod (8105). sh scripts/reset-kantarci-reisen.sh <Ordner Kantarci-Reisen> <Screenshot.png> schneidet Logo und Seitensymbol zu (build-kantarci-reisen-assets.php), baut mit seed-kantarci-reisen.php alles neu und holt DM Sans auf den eigenen Server. Ohne Argumente nimmt es die vorbereiteten Bilder.

  • Startseite wie das Mockup: geteilter Hero (Petrol/Küstenfoto), drei Angebote als ganz klickbare Karten (ohne Nummern, auf Umuts Wunsch), „Gute Reisen beginnen mit einem Gespräch“ mit Anschrift, neu „So läuft’s“ in drei Schritten, Anfrage auf Petrol mit CMS-Formular reiseanfrage (Versand an info@kantarci-reisen.de, dazu Telefon, Reisezeitraum, Personen), reiseSoft mit echtem Screenshot im Browserrahmen und Knopf zur Plattform. Dazu Impressum (Reisevermittlung), Datenschutz, 404, klebender Kopf, Einblendungen.
  • „Reise anfragen“ ist ein Menü-Button (ADR 110) mit Pfeil, auf dem Telefon neben dem Menü-Knopf. Eigenes Menü-CSS darf .menu-list a kein Polster geben, ohne es dem Button zurückzugeben (.menu-list .menu-button).
  • Screenshot reiseSoft: gebaute Fassung von reiseSoft lokal (Port 4199) mit leerem Datenordner im Scratchpad gestartet, Ersteinrichtung mit Testzugang, scripts/demodaten.mjs (60 fiktive Vorgänge), Übersicht in 1440×900 @2x aufgenommen. Keine echten Kundendaten.

Geprüft: test:browser auf 8104 grün (HTML 66 KB). Lighthouse auf 8105 (Produktion): / mobil 98/100/100/100/100, Desktop 100 überall; /impressum überall 100 (reports/lighthouse-kantarci-reisen). Ganzseitige Screenshots 1440 und 390 px ohne Konsolenfehler und ohne waagrechtes Scrollen. Nicht geprüft: Formularversand per E-Mail.

Nachtrag (08.10.2026): Telefon 0170 2945935 (auch WhatsApp) von Umut; eine USt-IdNr. gibt es nicht. WhatsApp als Anfrageweg aus dem Baustein „Soziale Profile“ (Netzwerk in den Einstellungen, Link mit vorausgefülltem Text): Knopf unter „Über uns“ und neben dem Formular, Link im Fuß, runde Schaltfläche fest unten links (position:fixed im eigenen CSS). Datenschutz mit eigenem Punkt zu WhatsApp. „Inhaber“ steht nicht mehr im Fuß, nur im Impressum. Danach erneut test:browser grün (HTML 72 KB) und Lighthouse / mobil 98, sonst 100; /datenschutz überall 100.

Bekannt: Öffnungszeiten und Hoster sind unbekannt und stehen nirgends (Datenschutz nennt den Hoster allgemein). Die Domain kantarci-reisen.de existiert am 08.10.2026 im DNS nicht (NXDOMAIN bei Fritz.box, 1.1.1.1 und 8.8.8.8) – damit kommen weder Mails an info@kantarci-reisen.de an noch ist reisesoft.kantarci-reisen.de erreichbar. Vor dem Livegang klären. Das Reisefoto ist laut Paket KI-generiert, so im Bildnachweis. Grenzen des Kerns beim Aufbau: Textblöcke geben <span> nicht aus, eigenes CSS erlaubt keine Anführungszeichen in Werten, kein calc() in maxWidth, kein >* und keine ? in Attributselektoren.

Nächster Schritt: Inhalte mit Umut abstimmen, dann Livegang nach docs/LIVEGANG.md.

Fassung 1.26.0: Menüeinträge als Button (07.10.2026)

Anlass: Umut: Buttons im Menü – individuell wie im Editor, mit Symbol und Farbe, auf dem Telefon im aufgeklappten Menü oder neben dem Menü-Knopf.

Neu (ADR 110): In der Menüverwaltung je Eintrag „Als Button darstellen“ mit Form, Darstellung, Größe, Fläche/Schrift (auch beim Überfahren), Symbolfarbe, Symbol aus dem Fenster, Bewegung, „nur Symbol“ und Anzeige auf dem Telefon (im Menü / neben dem Menü-Knopf / dort nur als Symbol). Vorschau normal und beim Überfahren.

Geprüft: PHPUnit vollständig, npm test, npm run test:menu-buttons (Browser, axe, 1440 und 390 px, je ein Button neben dem Menü-Knopf und im aufgeklappten Menü), lint:twig, php-cs-fixer.

Bekannt: In schmalen Kopfspalten bricht der Button in die nächste Zeile (wie jeder Menüeintrag). Kern ab 1.26.0 nötig.

Fassung 1.25.0: 98 Symbole, Auswahlfenster, Symbol-Gestaltung unter Design (07.10.2026)

Anlass: Umut zu 1.24.0: „Du wolltest doch mehr Icons machen oder nicht? Und nicht alles so dort auflisten, erst onklick einblenden oder Modal das gefällt mir nicht. Dann brauche ich Icon-Einstellungen ausgefüllt, extra Farbe usw. Mach es doch durchdacht.“

Neu (ADR 109):

  • 98 Symbole statt 33 in sieben Kategorien, darunter Warenkorb, Euro, LKW, Paket, Karte, Kamera, Schloss, Werkzeug, Malerrolle, Baum, Solar, Strom, Auto, Warnung. Quelle public/assets/icons.js, npm run build:icons schreibt Twig und Validator.
  • Auswahlfenster: Im Panel nur das gewählte Symbol mit „Ändern …“. Das Fenster zeigt Kategorien als Reiter und Symbole mit Namen. Gilt für Button, Symbol und Symbolkarte.
  • Design → Symbol (je Gerät, mit Hover): Farbe, Fläche dahinter, Größe, Innenabstand und Ecken der Fläche, Drehung, Abstand zum Text.
  • Inhalt (Schema v26): Stellung, „Beim Überfahren: Ruhig / Rückt / Erscheint“, „Nur das Symbol zeigen“ (quadratischer Knopf, Beschriftung bleibt für Vorlesesoftware).

Geprüft: PHPUnit vollständig (616) grün, npm test (38) grün, npm run test:button-icons (Editor im Browser, inkl. axe im Fenster und gemessenem Aussehen), test:buttons grün, test:browser auf Hüfner (Entwicklungsmodus, Port 8102) grün mit HTML 148 KB. php-cs-fixer und lint:twig ohne Befund. Nicht geprüft: Lighthouse.

Bekannt: Dokumente mit Bewegung oder „nur Symbol“ brauchen einen Kern ab 1.25.0 (Schema v26). Die Gestaltungswerte (Farbe usw.) brauchen kein neues Schema, werden von älteren Kernen aber abgelehnt (unbekannte Stileigenschaft) – Installationen zuerst aktualisieren. test:browser verlangt den Entwicklungsmodus (Kopf x-kant-queries), nicht wie in der README beschrieben den Produktionsmodus.

Veröffentlicht: Release v1.25.0 auf git.kant-it.de mit Archiv und install.php, Prüfsumme nach dem Herunterladen bestätigt.

Hüfner aktualisiert (07.10.2026): Die lokale Installation läuft aus diesem Repository und hat damit 1.25.0. Auf der Startseite ist das LKW-Symbol der Vorteilsleiste (vorteil-3-icon) jetzt der Baustein „Symbol“ mit truck, gestaltet wie die drei Nachbarn (32 px, @hellgruen), statt „Eigenes HTML“. Geändert direkt in var/huefner.db (veröffentlicht, Sicherung var/huefner.db.vor-lkw) und in scripts/seed-huefner.php, damit ein Neuaufbau dasselbe ergibt. Geprüft im Browser (Symbol sichtbar, keine HTML-Bausteine mehr) und mit test:browser (grün, HTML 148 KB).

Nächster Schritt: Livegang Hüfner nach docs/LIVEGANG.md, die übrigen Installationen auf 1.25.0 aktualisieren.

Fassung 1.24.0: Symbol am Button wählbar, Kachelauswahl für Symbole (07.10.2026)

Anlass: Umut: „Buttons mit Icon? Geht das, wenn ja wo? Und einfacher Button ist ein Pfeil, wie ändere ich das?“ – dann: „Ja bitte umsetzen. Ein Satz Symbole, die man so alltäglich brauchen könnte, und schöne Vorschau-Auswahl, bitte kein Dropdown mit Suche usw.“

Neu (Kern, Schema v25, ADR 108):

  • Button → Inhalt → Symbol: Kacheln mit Vorschau in drei Gruppen (Pfeile, Kontakt, Allgemein), „Kein Symbol“ vorn. Darunter Symbol steht: Vor dem Text / Nach dem Text, nur wenn ein Symbol gewählt ist. Ohne Wahl bleibt der schräge Pfeil hinter dem Text – bestehende Seiten sehen unverändert aus, eigenes CSS auf .button-arrow greift weiter.
  • 13 neue Symbole im gemeinsamen Satz (Pfeil nach unten, Winkel, Kalender, Nachricht, Senden, Download, Dokument, Abspielen, Suche, Herz, Info, Plus, Person), damit 33 insgesamt.
  • Symbol und Symbolkarte wählen ihr Symbol mit derselben Kachelauswahl statt der Liste mit englischen Namen.
  • public/assets/icons.js (Pfade, deutsche Namen, Gruppen, Auswahl), in der Importmap eingetragen. Stile in button-controls.css. site.css: Hülle heißt button-icon.

Geprüft: PHPUnit vollständig (609 Tests) grün, darunter zwei neue Tests in CmsTest (Ausgabe mit Standardpfeil, Telefon vorn, kein Symbol; Ablehnung unbekannter Symbole und älterer Schemata). npm test (38) grün, dazu tests/icons.test.mjs (Twig, Editor und Validator kennen dieselben Symbole) und repairSchema hebt auf v25. npm run test:button-icons im Browser auf einer Kopie der Pilotdaten: Kachel wählen, Stellung umschalten, Pfeiltasten und Enter, Rückgängig/Wiederholen, kein Symbol, Speichern als v25, nach Neuladen gleich; keine Seitenfehler. Screenshots reports/button-icons/. php-cs-fixer und lint:twig ohne Befund. Nicht geprüft: Lighthouse und test:browser (die öffentliche Ausgabe ändert sich nur bei gewähltem Symbol um ein Inline-SVG gleicher Größe).

Bekannt: Ein Dokument mit Symbolwahl lässt sich auf einem Kern vor dieser Fassung nicht speichern (Schema v25) – Installationen zuerst aktualisieren. Keine Markenlogos im Satz.

Nächster Schritt: Push von main und Tag v1.24.0, Releasearchiv auf der Releaseseite veröffentlichen, dann die Installationen aktualisieren (vor Hüfner, damit dort Symbole gewählt werden können).

Fassung 1.23.1: neunte Installation „Hüfner Gartenbau“, Knöpfe mit tel: und mailto:, Pfeil in der Schriftfarbe (06.10.2026)

Anlass: Umut: „Hier liegt ein Designentwurf für Hüfner Gartenbau. Kannst du mir db und media erstellen für KantSites bitte. […] Effekte rein. Nichts am Code ändern, sondern alles an KantSites-Designelementen anpassen/bauen. Falls was nicht geht, Rückmeldung.“ Vorlage: Downloads/huefner-website/huefner-website (statisches index.html, 16 Fotos, Logo).

Neu (nur Inhalt, kein Kerncode): Datenbank var/huefner.db, Medien var/huefner-media, Bildquellen var/huefner-assets, Startkonfigurationen huefner (Port 8102, dev) und huefner-prod (Port 8103). sh scripts/reset-huefner.sh <Ordner des Entwurfs> kopiert die Bilder und schneidet den Baum aus dem Logo als Seitensymbol (build-huefner-assets.php), baut mit seed-huefner.php alles neu und holt Barlow und Barlow Condensed auf den eigenen Server. Ohne Argument nimmt es die schon vorbereiteten Bilder.

  • Startseite mit allen elf Abschnitten des Entwurfs: Hero mit Verlauf und Vorteilsband, Collage, Leistungs- und Referenzkacheln (Foto zoomt, Kachel bekommt Schatten), Chips, Ablauf mit gestrichelter Linie, Fuhrpark, Über uns, Karriere (Stellen als Links mit E-Mail-Betreff), Instagram-Raster, Abschlussband, Kontakt mit dem CMS-Formular anfrage (Versand an info@huefner-gartenbau.de). Dazu Impressum, Datenschutz, 404, Menü mit Sprungmarken, klebender Kopf, Einblendungen beim Scrollen.
  • Inhaltliche Ergänzungen statt der Platzhalter der Vorlage: Öffnungszeiten Mo–Fr 8–17, Sa 8–12 (von Umut, auch als strukturierte Daten), Firmengeschichte und Impressum aus dem Handelsregister (HRB 742159, AG Mannheim, GF Florian Hüfner; Simone Hüfner war nur bis 27.04.2026 Prokuristin), Hoster IONOS, alle Fotos eigene Aufnahmen. Kennzahlen „[XX] Jahre“/„[XXX] Projekte“ ersetzt durch Aussagen der Vorlage (100 % Detailarbeit, Bagger bis 15 t, ein Team). Keine USt-IdNr. bekannt, deshalb keine Zeile.

Grenzen des Kerns:

  • Nachtrag, gleicher Tag – auf Umuts Wunsch im Kern behoben (ADR 107): Knöpfe und andere verlinkbare Bausteine nehmen tel: und mailto: an, und der Pfeil im Knopf hat die Schriftfarbe statt der Akzentfarbe. Die Anrufknöpfe bei Hüfner sind jetzt echte Knöpfe, der Pfeil braucht keine eigene Regel mehr. Vorher: Textlink im Knopf-Aussehen und color:inherit im CSS des Knopfs.
  • Kein LKW im Symbolsatz: Das Symbol steht als „Eigenes HTML“ mit dem SVG der Vorlage.
  • Leiste und Kopf kleben gemeinsam (die Einstellung gilt für den ganzen Kopfbereich).
  • Zeilen sind durchsichtig, farbige Karten deshalb Spalten mit Flex.
  • Eigenes CSS ohne @media/*: gerätespezifische Werte über die Gestaltungswerte (Bildhöhe, Raster-Spalten, Ränder je Gerät).
  • Das HTML-Budget von 150 KB (test:browser) war mit dem CSS je Kachel überschritten (152 KB). Die gemeinsamen Regeln stehen jetzt einmal am Abschnitt, Ergebnis 147,5 KB.

Geprüft: Lighthouse im Produktionsmodus (Port 8103, je drei Läufe, Median): Startseite mobil 97 / Desktop 98, Impressum und Datenschutz 100 / 100; Barrierefreiheit, Best Practices, SEO und agentisches Browsing überall 100. test:links ohne toten Verweis, test:browser grün (axe in sechs Ansichten, Tastatur, ohne JavaScript, Budget). Ganzseitige Bildschirmfotos bei 1440, 820 und 375 px gegen den Entwurf verglichen, kein waagerechtes Scrollen, keine Konsolenfehler. Zoom, Schatten, Chip- und Knopf-Hover sowie das Klappmenü per Playwright nachgemessen. Keine Felddaten, kein INP. Das Formular wurde nicht abgeschickt (Mailversand nicht ausprobiert).

Nächster Schritt: Livegang nach docs/LIVEGANG.md (Mailversand einmal echt testen). Die Hüfner-Datenbank braucht einen Kern mit ADR 107 (tel: an Knöpfen).

Kantarci Markt: Demo der Eröffnungsseite mit Angeboten aus der App (24.09.2026)

Anlass: Umut: „Setze mal die demoseite um wie du es dir vorstellst ich überlasse dir alles. Links und logo zu playstore und ios app, wilhelm drapp str. in baden baden, Rest Platzhalter“. Die Angebote kommen aus der Web-Ansicht seiner App: https://app.kantarci-markt.de/embed/angebote.php?ton=dunkel, dazu sein Skript für die Höhe.

Neu (nur Inhalt, kein Kern): /eroeffnung im Seed-Skript, veröffentlicht, aber noindex und nirgends verlinkt. Aufbau: Einstieg „Jetzt geöffnet“ mit zwei Wegen (Angebote, Anfahrt) und Öffnungszeiten, Angebote als eingebettete App-Ansicht, App-Bereich mit Knöpfen für App Store und Google Play, das Versprechen, Besuch (Adresse mit „Route planen“, Öffnungszeiten, Kontakt), Abschluss. Zur Eröffnung soll dieser Inhalt als geplanter Entwurf auf die Startseite, /eroeffnung fällt dann weg.

  • Einwilligung: keine nötig. Consent::granted() lässt jede Gruppe durch, solange die Website keine Einwilligungsleiste hat (bei Kantarci Markt aus). Meine frühere Aussage, der Baustein verlange immer eine Einwilligung, war falsch. Eine Option „Eigener Dienst“ braucht es erst, wenn die Seite einmal eine Leiste bekommt.
  • Höhe: Umuts Skript hängt als eigenes Skript am Baustein. Es lief zu spät: Die App meldet ihre Höhe beim Laden (gemessen 332 ms), das Skript lief als defer-Datei erst kurz vor DOMContentLoaded (402 ms), danach meldet die App nur noch Änderungen. Jetzt fragt das Skript nach dem Start per km-angebote-height-request nach und lädt den Rahmen als Rückfall nach 600 ms einmal neu, falls keine Meldung kam. Die App kennt die Nachfrage noch nicht, deshalb greift derzeit der Rückfall (zweiter Abruf der Web-Ansicht).
  • Hintergrund: Die Web-Ansicht ist für hellen Grund gebaut (dunkle Überschrift, weiße Karten), ton=dunkel und ton=hell liefern dieselbe Seite. Der Angebotsbereich steht deshalb auf Sand statt auf Waldgrün. & iframe{border-radius:0;background:transparent} am Baustein nimmt Rundung und Tönung aus site.css.
  • Platzhalter: Öffnungszeiten (Mo–Sa 8–20 Uhr, So geschlossen), „Telefon folgt“, Store-Links auf die Suche nach „Kantarci Markt“ im jeweiligen Store, Store-Knöpfe als eigene schwarze Knöpfe ohne die Logos von Apple und Google. Alles oben im Abschnitt des Seed-Skripts.
  • scripts/audit.mjs kennt CMS_HOST_RULES (etwa MAP localhost 127.0.0.1): Die App erlaubt das Einbetten nur von kantarci-markt.de und http://localhost:*, der Server lauscht auf 127.0.0.1.

Geprüft: Lighthouse im Produktionsmodus über localhost:8101, Rahmen geladen: /eroeffnung mobil 96 / Desktop 100, Barrierefreiheit, Best Practices und agentisches Browsing 100. SEO 69 nur wegen des gewollten noindex (is-crawlable), keine Konsolenfehler. Die Startseite blieb bei 98 / 100 und überall sonst 100. Im Browser: Rahmen 448 px (Desktop) bzw. 971 px (375 px) hoch, kein Scrollbalken, kein waagerechtes Scrollen. Sicherheitsrichtlinie der Seite: frame-src https://app.kantarci-markt.de, script-src 'self'.

Nachtrag, gleicher Tag – Popup statt Link (App-Repository C:\Django Project\Supermarkt): Umut: Angebote ohne Bild wirkten durchsichtig, „Unsere Angebote“ war weiß, und statt eines Links soll ein Popup die Einzelheiten hier zeigen. Die App-Ansicht hatte inzwischen ton, titel, app, bg bekommen; ton=dunkel machte die Überschrift weiß. Eingebunden ist sie jetzt mit ?titel=0&app=0 (eigene Überschrift der Seite, kein Knopf nach außen). In der App (public/embed/angebote.php, noch nicht committet, nicht ausgeliefert): Karten sind <article> mit einem Knopf im Namen statt <a>, ein natives <dialog> zeigt Bild, Kategorie, Name, Gültigkeit, Beschreibung und Preise auf Höhe der Karte (das iframe ist so hoch wie sein Inhalt) und meldet per km-angebote-focus, wo es steht. Die Ansicht beantwortet km-angebote-height-request, Angebote ohne Bild haben zartes Grün statt Creme, und ohne Datenbank gibt es die leere Ansicht statt eines Fatal Errors (count(null)). embed/angebote.js und das Skript im Seed kennen Nachfrage und Hinscrollen. Geprüft mit der lokalen App-Fassung (Anfragen im Browser umgeleitet, Beispieldaten im lokalen Cache): keine Links mehr, Popup im sichtbaren Bereich (auch nach Scrollen), Fokus auf „Schließen“ und zurück zur Karte, Esc und Klick auf den Rand schließen, axe ohne Befund, nur ein Abruf der Ansicht (kein Rückfall-Neuladen). Live wirkt es erst nach Push und App-Update auf dem Server.

Offen, von Umut: echte Store-Adressen (und auf Wunsch die offiziellen Badges), Öffnungszeiten, Telefon, Eröffnungsdatum, Impressumsangaben. App-Änderungen committen, pushen und auf dem Server aktualisieren.

Fassung 1.23.0: achte Installation „Kantarci Markt – Neueröffnung“, content im eigenen CSS, Schriften ergänzt statt überschrieben (23.09.2026)

Anlass: Umut: „erstellst du bitte bald verfügbar site nach dem prinzip kannst gerne dazu implementieren“ – dazu das Paket Kantarci-Markt-Landingpage.zip (statische Seite: Logo-Kopf, Hero „Frische bekommt eine neue Adresse.“ mit Marktfoto, Tomatenkarte und Stempel, Versprechen auf Waldgrün, Abschluss „Frische. Qualität. Faire Preise.“, Fuß).

Neu. Datenbank var/kantarci-markt.db, Medien var/kantarci-markt-media, Bildquellen var/kantarci-markt-assets, Startkonfigurationen kantarci-markt (Port 8100, dev) und kantarci-markt-prod (Port 8101). sh scripts/reset-kantarci-markt.sh baut alles neu: build-kantarci-markt-assets.mjs (Logo zugeschnitten auf 600 px, Seitensymbol und Stempel als PNG über Chromium), seed-kantarci-markt.php (Startseite, Impressum, Datenschutz, 404, Kopf, Fuß, Einstellungen), dann DM Sans auf den eigenen Server. Alles aus gewöhnlichen Bausteinen, kein Plugin.

  • Abweichungen: Manrope entfällt (DM Sans an seiner Stelle, eine Schrift weniger). Die Bildzeile „Vorfreude auf frische Vielfalt.“ steht höher als in der Vorlage, weil Stempel und Tomatenkarte sie dort verdeckten. Das große „K“ im Abschluss ist ein Pseudo-Element, kein Text. Keine LocalBusiness-Daten, solange der Markt nicht geöffnet hat.
  • Impressum und Datenschutz sind Pflicht und angelegt. Offen, im Text als „NOCH ZU PRÜFEN“: Betreiber/Rechtsform, Hausnummer, Vertretung, Register, Telefon, USt-IdNr., ob info@kantarci-markt.de existiert (die Domain hat Mailserver), Quelle der beiden Fotos, Hosting.

Im Kern dazu gebaut:

  • Eigenes CSS: content mit kurzem Text (CustomCode), damit ::before/::after möglich sind. Eng begrenzt, siehe Nachtrag zu ADR 028.
  • fonts.css wird ergänzt statt überschrieben (FontLibrary::combine): Ein Abruf ersetzt nur die Regeln der geholten Familien. Vorher hätte das Holen von DM Sans Lato und Cormorant der anderen lokalen Installationen aus der gemeinsamen Datei gelöscht.
  • Variable Schriften einmal statt je Schnitt: DM Sans liefert Google als eine Datei für alle Schnitte. Der Kern verlinkte und lud sie dreimal vor (111 KB statt 37 KB), das kostete am Telefon den LCP. Jetzt eine @font-face mit Gewichtsbereich 400 700, und preload() nennt nur Dateien, die fonts.css tatsächlich verlinkt.
  • Bekannt, nicht geändert: Zeilen sind im Kern immer durchsichtig (.block-row{background:transparent!important}). Die grüne Karte „Wir öffnen bald“ ist deshalb eine Spalte.

Geprüft: Lighthouse im Produktionsmodus (Port 8101, je drei Läufe, Median): Startseite mobil 97 / Desktop 100, Impressum und Datenschutz 100 / 100; Barrierefreiheit, Best Practices, SEO und agentisches Browsing überall 100. Vorher 87 mobil wegen CLS 0,19 (Logo im Kopf bekam seine Breite erst nach dem Laden) und der dreifachen Schrift. Bildschirmfotos 1440 px und 375 px ohne waagerechtes Scrollen und ohne Konsolenfehler, Impressum und 404 angesehen. Neue Tests in CmsTest (content erlaubt/abgewiesen, combine, Preload bei variabler Schrift); volle PHPUnit-Suite (596) und npm test (36) grün. Keine Felddaten, kein INP.

Nächster Schritt: Die offenen Angaben im Impressum von Umut bekommen; auf Wunsch ein Formular „Zur Eröffnung benachrichtigen“ (braucht dann Einwilligungstext und Datenschutzabsatz).

Fassung 1.22.5: alte Browserchecks wieder grün, Kopfbereich von kant-it.de wieder bearbeitbar (22.09.2026)

Anlass: Umut: „räum den Strukturbaum-Fehler auch auf und Release 1.22.5“.

  • Strukturbaum: test:admin, test:buttons und test:navigation-ux klickten direkt in den Strukturbaum. Seit 1.11 zeigt die Seitenleiste aber eine Ansicht auf einmal, Standard „Elemente“, und der Baum war versteckt. Jetzt wählen die Checks den Reiter „Struktur“, dann den Baustein und „Bearbeiten“, wie editor-check.mjs. Der Editor selbst war richtig.
  • Echter Fehler, dabei gefunden: Der Kopfbereich in var/kantit.db (und damit vermutlich auf kant-it.de) trägt die Hamburger-Umschaltung (collapse) bei Schema v19, erlaubt ist sie erst ab v22. Die Website zeigt ihn, aber der Editor bekam für die Vorschau „Unbekannte Hamburger-Umschaltung oder Dokumentschema vor v22“, und Speichern wäre ebenso abgelehnt worden: Der Bereich war nicht bearbeitbar. Jetzt hebt repairSchema() (editor-model.js) ein geladenes Dokument auf die Fassung, die seine Bausteine und Eigenschaften verlangen. Das ist dieselbe Hebung, die der Editor beim Einstellen ohnehin macht. Die Umstellung auf formatierten Text (v12/v17) kommt nie von selbst, weil sie die Bedeutung ändert. Beim nächsten Speichern ist das Dokument korrigiert. Alle anderen lokalen Installationen hatten keine solchen Dokumente (geprüft mit dem Validator über alle Seiten und Bereiche).
  • Weitere veraltete Stellen in test:admin: Der Knopf „Bewegung testen“ heißt seit 1.11 „Bewegung in der Vorschau abspielen“ (#preview-motion), auch ein Hilfetext im Editor nannte noch den alten Namen. Der Slider-Teil erwartete, dass „Bewegung reduzieren“ die Automatik anhält. Seit 1.11 gilt das nur mit „Systemvorgabe beachten“. Der Check prüft jetzt das Verhalten, das die Einstellung der Website verspricht.

Geprüft: test:admin (vollständig, auch der öffentliche Teil), test:buttons, test:navigation-ux und test:editor grün. Neuer Einheitstest für repairSchema in tests/editor-model.test.mjs.

Fassung 1.22.4: Ziehknopf im Menü neben der Zusammenfassung (22.09.2026)

Anlass: Umut: „ja räum das auf und Release 1.22.4“ – die bekannte axe-Meldung nested-interactive im Menü-Editor.

  • Der Ziehknopf eines Menüeintrags stand in der <summary>, also ein Knopf in einem Knopf. Screenreader lesen so etwas unzuverlässig vor, und die Tastatur erreicht es nicht sauber. Jetzt steht er als Geschwister vor <details>, per CSS über die linke Ecke der Zusammenfassung gelegt (menu-entry.html.twig, createRow in menu-editor.js). Aussehen und Bedienung bleiben gleich: Ziehen mit Maus, Stift und Finger, Pfeiltasten auf dem Knopf.
  • Der Hinweis zu fehlenden Sprungzielen im Menü und die Hilfe zum Feld „Sprungziel“ im Editor nennen jetzt die richtige Stelle (Sektion, Zeile oder Spalte) und den Weg über die Auswahl im Menü.
  • test:admin: Die Fehlermeldung nach einem abgewiesenen Speichern wurde direkt nach der Serverantwort abgefragt, sie erscheint aber erst, nachdem die Antwort gelesen ist. Jetzt wartet der Test auf sie. Der Check scheitert weiterhin an der älteren Stelle beim Klick in den Strukturbaum des Editors.

Geprüft: Die Editor-Prüfung prüft die ganze Menüseite mit axe, ohne Ausnahme. Sie stellt sicher, dass in keiner Zusammenfassung ein Knopf steht, und verschiebt einen Eintrag per Pfeiltaste (Fokus bleibt auf dem Knopf). Bildschirmfoto reports/editor/menu-editor.png angesehen.

Fassung 1.22.3: die übrigen abgetippten Adressen (22.09.2026)

Anlass: Umut: „die offenen Punkte auch umsetzen und Release 1.22.3“ – die drei Reste aus 1.22.2.

  • Sprungziel im Menü: Eine Auswahl aus den Ankern der verknüpften Seite, beschriftet mit der ersten Überschrift des Bausteins, etwa „So erreichen Sie uns (#kontakt)“. Die Liste liefert GET /admin/api/sprungziele/{id} (Rolle wie die Menüs). Wechselt die Seite, lädt die Liste neu. Ein gespeicherter Anker, den es nicht mehr gibt, bleibt als „(auf der Seite nicht gefunden)“ sichtbar. Ohne Skript bleibt das Textfeld.
  • Linkfelder in der Plugin-Verwaltung und die Adresse eines eigenen Menülinks: Das Textfeld bleibt, weil eine fremde Adresse getippt wird. Daneben stehen „Seite wählen“ und „PDF wählen“ (link-field.js, Markup data-link-field/data-link-input).
  • Kategorien und Schlagwörter eines Beitrags: Die vorhandenen Namen stehen als Knöpfe unter dem Feld. Ein Klick nimmt einen Namen auf oder heraus (term-chips.js), neue Namen tippt man weiter ein. So entstehen nicht „Projekt“ und „Projekte“ nebeneinander.

Bekannt, nicht neu: axe meldet im Menü-Editor nested-interactive, weil der Ziehknopf in der Zusammenfassung jedes Eintrags steckt. Die Editor-Prüfung prüft dort nur die neuen Felder.

Geprüft: neue Tests EditorSecurityTest::testMenuAnchorsAndPostTermsAreOfferedForChoosing und …testPluginSettingsForLinksAndImagesAreChosenNotTyped. Die Editor-Prüfung wählt im Menü eine Seite als eigenen Link, prüft die Sprungziel-Auswahl eines neuen Seitenlinks, legt einen Beitrag mit Kategorie an und nimmt die Kategorie per Klick auf und wieder heraus (jeweils mit axe).

Fassung 1.22.2: Auswahl statt abgetippter Kennungen und Adressen (22.09.2026)

Anlass: Umut zu den Beitragseinstellungen: „nicht dein ernst? ID aus Mediathek? schöne auswahl rein! auch bei Beitragsbild“, dann: „Andere bereiche prüfen ob es sowas noch gibt!“

Die ganze Verwaltung und der Editor wurden nach Feldern durchsucht, in die man eine Kennung, eine Adresse oder einen technischen Namen tippen musste, der auf etwas anderes zeigt. Umgebaut:

  • Vorschaubild beim Teilen und Beitragsbild (Seiten und Beiträge → Einstellungen): Bildfeld mit Vorschau, „Bild wählen oder hochladen“ im Mediathek-Dialog, „Entfernen“ (admin/media-field.html.twig, media-field.js, Twig-Funktion kant_media). Die Kennung steht nur noch im versteckten Feld.
  • Plugin-Einstellungen der Art Bild: dasselbe Bildfeld statt „Kennung aus der Mediathek“.
  • Editor, Formular-Baustein, Sektions-Baustein, Beitragsliste (Kategorie, Schlagwort): Auswahllisten statt Adressen. Die Listen kommen mit /api/design (choices). Eine gespeicherte Adresse, die es nicht mehr gibt, bleibt als „(nicht mehr vorhanden)“ sichtbar.
  • Linkziele im Editor (Button, Bild, Symbolkarte, CTA, Symbol, Link-Felder von Plugins) und Links im Textbaustein: „Seite wählen“ mit Seitensuche (openPageDialog in page-picker.js) neben „PDF aus Mediathek wählen“. Freie Adressen bleiben möglich. Der Schalter „In neuem Tab öffnen“ erscheint bei Plugin-Feldern nicht mehr, deren Schema kennt ihn nicht.
  • Weiterleitungen, neue Adresse: Seitensuche. Ein anderer Pfad (etwa kategorie/projekte) lässt sich weiter eintippen (data-free im Seitenwähler).
  • Formular-Editor: „Antwortadresse aus Feld“ wählt aus den E-Mail-Feldern, „Nur zeigen, wenn“ aus den Auswahlfeldern weiter oben, mit Kästchen für die Antworten. „Bild je Option“ öffnet den Mediathek-Dialog mit Vorschau statt einer Aufklappliste. Ohne Skript bleiben die Textfelder, die gespeichert werden.

Bewusst getippt bleibt: was an der Stelle erst entsteht (Adresse einer neuen Seite, eines Formulars, einer Sektion, Farb- und Schriftschlüssel, technischer Feldname, Anker), die alte Adresse einer Weiterleitung, Einbettungsadressen, Schlüssel für Turnstile. Noch offen: Link-Einstellungen von Plugins in der Plugin-Verwaltung (Textfeld), Sprungziel im Menü, Vorschläge vorhandener Kategorien und Schlagwörter in den Beitragseinstellungen.

Geprüft: neue Tests EditorSecurityTest::testImageSettingsOfAPostArePickedFromTheLibraryNotTypedAsAnId und …testTheEditorPicksFormsSectionsAndTermsFromListsInsteadOfTypedAddresses. Die Editor-Prüfung (npm run test:editor) wählt jetzt im Browser ein Vorschaubild und speichert es. Sie wählt eine Seite als Linkziel (mit axe) und prüft, dass Formular, Kategorie und Schlagwort Auswahlfelder sind. Im Formular-Editor wählt sie Bedingung, Antwortadresse und ein Optionsbild, legt das Formular an und liest es wieder (mit axe).

Fassung 1.22.1: vergrößerbare Galerie mit einem Haken (22.09.2026)

Anlass: Umut: „Bei Klick vergrößern bei Galerie muss man da alles einzeln machen? kann man bei Galerie das mit einem Klick für alle elemente machen?“

  • Der Haken „Bilder vergrößerbar“ an der Galerie gilt jetzt für jedes Bild darin, das unter „Beim Klick“ nichts selbst gewählt hat. Das gilt auch für später hinzugefügte Bilder. Bisher schaltete er nur das Blättern im Popup ein, und jedes Bild musste einzeln auf „Bild vergrößern“ gestellt werden.
  • Eine eigene Wahl geht vor: „Nichts tun“ bleibt still, ein Link bleibt ein Link. „Nie eingestellt“ und „Nichts tun“ sind im Dokument unterscheidbar, weil der Editor click erst beim Ändern speichert. Bestehende Galerien ändern sich deshalb nur dort, wo der Haken gesetzt ist und ein Bild noch nie eingestellt wurde.
  • Der Editor zeigt bei einem solchen Bild „Bild vergrößern“ als aktuelle Wahl, nach derselben Regel wie die Vorlage (galleryZoom in blocks/block.html.twig).

Geprüft: neuer Test CmsTest::testAZoomableGalleryMakesEveryImageWithoutItsOwnChoiceZoomable (ohne Wahl, „Nichts tun“, Link, Galerie ohne Haken), volle Kette vor dem Release.

PDFs in der Mediathek, als Linkziel und im Betrachter (22.09.2026)

Anlass: Umut: „kann man aktuell PDFs hochladen und als Button verlinken? … und ein Inline PDF Viewer wie Bild-Popup“, danach: „ja setz um, einfacher Browser-Betrachter“.

  • Hochladen: Das Upload-Feld der Mediathek nimmt jetzt neben JPEG, PNG und WebP auch PDFs bis 20 MB an. Ob eine Datei ein PDF ist, entscheidet ihr Inhalt (%PDF- in den ersten 1024 Bytes), nicht die Endung. Die PDFs stehen in einer eigenen Tabelle document (Migration 20260917000100) und nicht in media. Jede Bildauswahl erwartet dort Varianten mit Breite und Höhe. Die Datei liegt unverändert als <id>.pdf im Medienordner und geht deshalb mit jeder Sicherung mit. Die Mediathek zeigt die PDFs unter den Bildern, mit Titel und Archivieren.
  • Ausliefern: /media/<id>.pdf kommt mit Content-Type: application/pdf, nosniff, inline und dem Titel als Dateinamen, X-Robots-Tag: noindex und frame-ancestors 'self': Einbetten darf es nur die eigene Website. Archivierte PDFs bleiben erreichbar, wie Bilder.
  • Verlinken: Jedes Linkziel (Button, Bild, Symbolkarte, CTA, Symbol) hat den Knopf „PDF aus Mediathek wählen“ mit einer Liste, Suche und Hochladen direkt im Dialog. Ins Feld kommt die Adresse /media/<id>.pdf, ein gewöhnlicher lokaler Pfad, also ohne neue Schemaversion.
  • Betrachter: public/assets/lightbox.js (dieselbe Datei wie der Bildbetrachter, damit eine Seite kein Skript mehr lädt) zeigt ein verlinktes PDF in einem <dialog> mit <iframe> und nutzt dafür den PDF-Betrachter des Browsers, dazu Herunterladen, „Im neuen Tab“ und Schließen. Wo der Browser keinen eingebauten Betrachter hat (navigator.pdfViewerEnabled) oder das Gerät ein Touchgerät ist, öffnet sich das PDF im neuen Tab: Android-Chrome hat keinen Betrachter, iOS-Safari zeigt im Rahmen nur die erste Seite. Ist „In neuem Tab öffnen“ gesetzt oder das Skript nicht geladen, bleibt es ein normaler Link. Skript und frame-src 'self' kommen nur auf Seiten, deren Dokumente (auch Kopf, Fuß, Sektionen und Fließtext) auf ein PDF der Mediathek zeigen.
  • Grenzen: Hochgeladene PDFs sind über ihre Adresse öffentlich. Kein PDF.js, also sieht der Betrachter je nach Browser anders aus. Chrome zeigt im Kopf des Betrachters die Kennung aus der Adresse, nicht den Titel. PDFs werden nicht gelöscht, nur archiviert.

Geprüft: zwei neue Tests (CmsTest::testAPdfFromTheLibraryIsALinkTargetAndOpensInTheViewer, EditorSecurityTest::testPdfsGoThroughTheMediaUploadAndTheEditorPicker: Ablehnung von Nicht-PDFs, Kopfzeilen, 404, Skript und Richtlinie nur mit PDF-Link, Formular-Upload, API mit Token, Archiv). Im Browser mit der Pilotkopie: Klick auf den Button öffnet das zweiseitige PDF im Popup, Schließen gibt den Fokus an den Button zurück, bei 375 px mit Touch öffnet es einen neuen Tab. Der Auswahldialog im Editor ist nur per Syntaxprüfung geprüft, nicht im Browser. Dafür wäre eine Anmeldung nötig.

Siebte Installation: Landingpage „KANTARCI Immobilien – Mietanfrage“ (18.09.2026)

Anlass: Umut: „neues Projekt wir brauchen eine einfache landingpage nach gleichem prinzip. einfache landingpage impressum und datenschutz notwendig? baus ein“ – dazu das Paket KANTARCI-Immobilien-Landingpage.zip (Next.js/Vinext für Cloudflare mit D1-Datenbank: Kopf, dunkler Hero, Werteleiste, Mietanfrage, Fuß, ein Banner als einziges Bild). Später: „Formular gefällt mir noch nicht kannst du ein multipage formular machen ggfls mit icon checkbox“ und die Anbieterdaten (Geschäftsführer Ridvan Kantarci, Ooser Hauptstr. 51, 76532 Baden-Baden, info@kantarci-immo.de).

Neu. Datenbank var/kantarci-immobilien.db, Medien var/kantarci-immobilien-media, Bildquellen var/kantarci-immobilien-assets (das Banner, daraus zugeschnitten Logo, Hausmotiv und Seitensymbol, dazu options/ mit 14 Bildkarten), Startkonfigurationen kantarci-immobilien (Port 8098, dev) und kantarci-immobilien-prod (Port 8099). sh scripts/reset-kantarci-immobilien.sh schneidet die Bilder zu (scripts/build-kantarci-assets.php, GD), zeichnet die Bildkarten (node scripts/build-kantarci-options.mjs, Chromium), migriert und baut mit scripts/seed-kantarci-immobilien.php Gestaltung, Kopf, Fuß, Startseite, Impressum, Datenschutz, 404, Einstellungen und das Formular mietanfrage auf. Schriften Arial und Georgia (System).

  • Impressum und Datenschutz sind Pflicht: geschäftlicher Auftritt (§ 5 DDG) und ein Formular mit personenbezogenen Daten (Art. 13 DSGVO). Offen und im Text als „NOCH ZU PRÜFEN“ markiert: Handelsregister, Telefonnummer, USt-IdNr., Erlaubnis nach § 34c GewO (nur bei Vermittlung/Verwaltung für Dritte), Urheber des Hausfotos, Hosting- und Mail-Auftragsverarbeiter, Löschfrist.
  • Mietanfrage in drei Schritten mit Zusammenfassung: Haushalt (Bildkarten Allein, Zu zweit, Familie mit Kindern, Wohngemeinschaft; Personenzahl nur bei den beiden letzten), Wohnwünsche (Zimmer als Bildkarten, Ausstattung als Mehrfachauswahl mit sechs Bildkarten, Einzug, Warmmiete, Objekt), Kontaktdaten. Der Einzug ist eine Auswahl statt eines Datums – der Formularbaukasten kennt kein Datumsfeld.
  • E-Mail: Die Vorlage nennt immo@kantarci-immobilien.de, die Domain existiert aber nicht (kein DNS, 18.09.2026). Überall steht jetzt info@kantarci-immo.de (MX vorhanden), auch als Empfänger der Anfrage.
  • CMS-Lücken beim Aufbau: eigenes CSS kennt kein @media – Raster, die am Telefon einspaltig werden sollen, laufen über repeat(auto-fill,minmax(…)). Verschachtelte Selektoren erlauben keine Umlaute, also auch kein [data-label="Gewünschter Einzug"] (Feld heißt deshalb „Einzugstermin“). Die Zusammenfassung ist selbst ein .form-step; wer die Schritte zweispaltig setzt, muss .form-summary-body über beide Spalten legen. <small> in einem Textbaustein wird entfernt. Die Fehlermeldung des Formulars duzt („Bitte prüfe …“), die Seite siezt.

Geprüft: Aufbau per Reset-Skript fehlerfrei; Startseite, Impressum, Datenschutz 200, unbekannte Adresse 404; im Browser (1280 px und 375 px, kein seitliches Scrollen) alle drei Schritte, Bedingung Personenzahl, Zusammenfassung und Absenden – die Anfrage liegt vollständig in form_submission. Mailversand ist lokal nicht eingerichtet. Lighthouse und axe noch nicht gelaufen.

Adresse der Website in der Verwaltung (21.09.2026)

Anlass: Umut: „ja bau die Einstellung in der Verwaltung ein sodass es in die env eingetragen wird und übernommen wird“ – mit oder ohne www wie die „WordPress-Adresse“, statt nur über cms:site:origin oder die .env von Hand.

  • System → Diese Installation → Adresse hat ein Feld „Adresse der Website“ und einen Link, der direkt auf die andere Schreibweise (mit/ohne www) umstellt. Gespeichert wird SITE_ORIGIN in .env über Environment::write. Das gilt ab der nächsten Anfrage, weil %env()% zur Laufzeit gelesen wird. Kommt SITE_ORIGIN aus der Server-Umgebung, gibt es kein Feld, und ein POST wird abgewiesen.
  • Schutz vor dem Aussperren: Vor dem Speichern fragt die Installation sich selbst unter der neuen Adresse an (OriginCheck, GET /.well-known/kantsites-origin). Zurückkommen muss ein HMAC aus APP_SECRET, den nur diese Installation kennt. Ohne DNS-Eintrag oder Zertifikat für www wird nichts geändert – sonst leitete die andere Schreibweise ins Leere, die Verwaltung eingeschlossen. Erst nach einer Ablehnung erscheint „Trotzdem übernehmen“ (etwa wenn der Server sich selbst nicht erreicht, oder bei http im lokalen Betrieb). Die Prüfadresse ist auch im Wartungszustand erreichbar.
  • Wechselt die Verwaltung dadurch den Host, ist dort eine neue Anmeldung nötig. Das Sitzungscookie gilt nur für die Schreibweise, unter der es entstand. Die Meldung sagt das.

Geprüft: zwei neue Tests in SystemScreenTest (Server-Umgebung ohne Feld, Ablehnung einer nicht erreichbaren Adresse ohne Schreiben in .env, falsche Eingaben, Prüfadresse; OriginCheck mit fremder und eigener Kennung). Im Browser mit der Pilotkopie bei 1280 und 375 px (kein seitliches Scrollen) Feld, Ablehnung und „Trotzdem übernehmen“ angesehen.

Favicon auch für die Google-Suche (21.09.2026)

Anlass: Umut: Google zeigt bei Elektro Bernhard weiter das KantSites-K, obwohl ein eigenes Symbol gesetzt ist – „mach das im Kern sobald man Favicon setzt alles ersetzen“.

  • Ursache: Das eigene Symbol gab es nur in 32 und 180 px, Google nimmt nur Vielfache von 48 px. Und /favicon.svg lag als Datei in public/, der Webserver lieferte also immer das K aus – dort hatte Google es beim ersten Besuch gefunden.
  • Neu: FAVICON_SIZES sind 32, 48, 180 und 192. Der Kopf verweist zusätzlich auf /favicon-192.png (sizes="192x192"). /favicon.ico liefert das 48er-PNG. /favicon.svg beantwortet jetzt die Anwendung: ohne eigenes Symbol das mitgelieferte Zeichen (liegt jetzt unter public/assets/favicon.svg), mit eigenem ein 302 auf /favicon-192.png. Fehlen bei älteren Installationen die neuen Größen, entstehen sie beim ersten Abruf aus der gespeicherten Einstellung. Wird das Symbol entfernt, gelten die liegengebliebenen PNGs nicht mehr.
  • Update: Das Archiv-Update kopiert public/ nur hinzu; ENTFALLEN_IN_PUBLIC löscht deshalb die alte public/favicon.svg, sonst bliebe sie auf dem Server und überdeckte die Route.
  • Danach bei Google: In der Search Console die Startseite unter „URL prüfen“ neu zur Indexierung einreichen. Google holt Favicons selten neu ab, das kann trotzdem Tage bis Wochen dauern.

Geprüft: neuer Test testGoogleGetsTheOwnSiteIconUnderEveryAddress und der bisherige Favicon-Test grün, Update-Tests unverändert (eine vorbestehende Warnung), php-cs-fixer sauber; lokal (ohne eigenes Symbol) /favicon.svg 200, /favicon.ico → /favicon.svg.

Favicon mit Zuschnitt, Pflichtseiten per Suche, neueste zuerst (18.09.2026)

Anlass: Umut: „Was ein Dropdown? nicht dein ernst? Sofort fixen über Mediathek auswählbar machen zurecht schneidbar machen und auch uploadbar dort machen!“ – dazu „Genauso Adresse Datenschutz und Impressum über search felder auswählbar machen“ und „Beiträge und Seiten sortieren absteigend also neueste zuerst“.

  • Symbol der Website hat einen eigenen Abschnitt in den Einstellungen statt einer Auswahlliste am Ende der Unternehmensdaten. Der Knopf öffnet die Mediathek (dort auch Upload), danach folgt ein Zuschnitt: Quadrat ziehen, Größe an der Ecke, per Regler oder mit +/−, Pfeiltasten zum Verschieben, Vorschau in 180 und 32 px. Gespeichert wird faviconCrop als links,oben,kante in Anteilen (links/oben von Breite/Höhe, Kante von der kürzeren Seite), renderFavicon() schneidet daraus die PNGs. faviconVersion hängt am Verweis; ohne Zuschnitt bleibt es bei den ersten acht Zeichen der Bildkennung, bestehende Verweise ändern sich also nicht.
  • openMedia() kennt needsAlt: false: Für das Symbol wird kein Alternativtext verlangt. Die Stile des Mediathek-Dialogs stehen jetzt in media-dialog.css statt in editor.css, damit der Dialog auch außerhalb des Editors funktioniert.
  • Datenschutz und Impressum werden über die Inhaltssuche gewählt wie die 404-Seite. Die Suche kann jetzt statt der Kennung den Pfad speichern (valueKey: 'path' bzw. data-value="path"), gespeichert wird also weiter die Adresse.
  • Seiten- und Beitragsliste der Verwaltung sortieren nach Anlagedatum absteigend. Die Einrückung der Unterseiten ist dort entfallen – ohne Baumreihenfolge hätte sie eine falsche Elternseite angedeutet.

Sechste Installation: der Redesign-Entwurf „EB Solar“ (16.09.2026)

Anlass: Umut: „bitte redesign aufsetzen ebsolar.de achte auf die richtlinien wie immer machs wie die anderen tagespflege, elektro-baernhard co“ – dazu das Paket ebsolar-redesign.zip (statische Website: Startseite, Projektübersicht, acht Projektseiten, eigene SVG-Symbole, Kontaktformular per mailto).

Neu. Neben Kant-IT, Elektro Bernhard, Tagespflege Moldovan, Maler Drews und DEEG steht jetzt EB Solar (Elektro Bernhard Regenerative Energie GmbH, Baden-Baden): Datenbank var/ebsolar.db, Medien var/ebsolar-media, Bildquellen var/ebsolar-assets (Logo, Favicon, Startbild, sechs Herstellerlogos, 31 Projektfotos, projects.json), Startkonfigurationen ebsolar (Port 8096, dev) und ebsolar-prod (Port 8097, APP_ENV=prod zum Messen). scripts/seed-ebsolar.php baut Gestaltung, Kopf- und Fußbereich, den Kontaktbereich als wiederverwendbare Sektion, Startseite, Projektübersicht, Solarcheck, Impressum, Datenschutz, 404, acht Projektbeiträge, Menü und zwei Formulare; sh scripts/reset-ebsolar.sh wirft weg, migriert und baut neu. Schrift ist Arial (System) wie in der Vorlage, es gibt nichts zu holen.

Aus der Vorlage übernommen: Farben (#b52c2c Rot, #252b30 Tinte, #f5f6f6 helle Fläche, #e6e8e8 Linie), Schriftgrößen, Abstände, Wortlaut und die Reihenfolge der Abschnitte – Hero mit Bildunterschrift über dem Foto, die vier Zusicherungen zwischen zwei Linien, drei Schritte, „Gutes Handwerk. Neue Energie.“ auf heller Fläche, Projektraster, Herstellerleiste, „Was kann Ihr Dach?“.

Bewusste Abweichungen.

  • Das Kontaktformular der Vorlage öffnet nur das E-Mail-Programm des Besuchers. Hier ist es das serverseitige Formular kontakt; die Anfrage liegt in der Verwaltung und geht an pv@elektro-bernhard.de.
  • Der Solarcheck ist im Paket nur ein Verweis auf ebsolar.de. Hier ist er eine eigene Seite mit dem Formular solarcheck: dieselben acht Schritte und dieselben Fragen wie im Original (abgerufen 16.09.2026), nur der Schieberegler für den Jahresverbrauch ist ein Zahlenfeld – ein Schieberegler ist im Formularbaustein nicht vorgesehen.
  • Die acht Projektseiten sind Beiträge der Kategorie „Projekte“ mit Galerie und Bildbetrachter; /projekte ist eine Beitragsliste. Die Projektart („Photovoltaik“, „Terrassenmontage · SolarEdge“) steht als Schlagwort am Beitrag und nicht als Kategorie: „vorheriges/nächstes Projekt“ folgt der ersten Kategorie, und die muss bei allen acht dieselbe sein, sonst führt die Navigation an Rheinstetten vorbei. Die Beitragsliste zeigt nur Kategorien, deshalb tragen die Karten anders als in der Vorlage keine Artzeile.
  • Impressum und Datenschutz sind eigene Seiten (Angaben vom Impressum der laufenden Website); was nur Elektro Bernhard beantworten kann, steht als „NOCH ZU PRÜFEN“ im Text.
  • Kein Cookie-Banner: es wird nichts von Dritten geladen.

Bildkarten im Solarcheck (17.09.2026). Umut: „du kannst so schöne svg's erstellen kannst du bitte noch schöne icons für den solarcheck machen statt checkboxe schöne bilder?“ Die fünf Auswahlfragen (Personen, Dachform, Dachfläche, Eindeckung, Speicher) zeigen jetzt Bildkarten statt Optionsfelder – 20 Symbole in Tinte und Rot auf heller Fläche, im Stil der übrigen Symbole der Website. scripts/build-ebsolar-options.mjs zeichnet sie als SVG und rastert sie mit Chromium auf 800×600 (4:3 wie die Karte), weil die Mediathek kein SVG nimmt; SVG und PNG liegen in var/ebsolar-assets/options/, das Aufbauskript hängt die PNGs per images an die Felder. Lighthouse /solarcheck danach: Leistung 98/98, Barrierefreiheit, Best Practices, SEO 100.

Im Kern behoben: Pfeile waren Schriftzeichen. Der Button haengte ein ↗ an, die Beitragsnavigation ein ←/→ – Buchstaben also, die je nach Schrift anders aussehen und mit der Schriftstaerke nicht mitwachsen. Der Symbolsatz (templates/blocks/icon.html.twig, DocumentValidator::ICONS) kennt jetzt drei Pfeile: arrow-right, arrow-left, arrow-up-right, gezeichnet wie die uebrigen 17 Symbole. Button und Beitragsnavigation setzen sie als eingebettetes SVG in denselben <span aria-hidden> wie bisher – & span{display:none} nimmt den Pfeil also weiterhin weg, wo eine Vorlage keinen will (DEEG). Die Groesse haengt an der Schrift des Knopfs (.button-arrow>svg{width:1.15em}), nicht an einer festen Zahl. Auf EB Solar tragen auch die Textlinks ihren Pfeil als Symbolbaustein neben dem Text; er zeigt auf dieselbe Adresse und ist als Schmuck von Vorlesesoftware und Tastatur ausgenommen.

Im Kern behoben: zweimal derselbe Selektor in eigenem CSS loeschte die erste Regel. CustomCode::parse() legte die Deklarationen je Selektor in einem Array ab – $rules[$suffix] = …. Standen & .post-card{border:0} und & .post-card{transform:none} untereinander, gewann die zweite, und border:0 verschwand ohne Meldung; im Projektraster von EB Solar kam dadurch der 1-px-Rahmen des Kerns zurueck. In CSS ist zweimal derselbe Selektor eine Kaskade, kein Widerruf: die Deklarationen werden jetzt in der Reihenfolge zusammengefuegt, in der sie standen. Dasselbe galt fuer &{…} neben den Eigenschaften des Elements selbst – auch das ging vorher verloren.

Im Kern behoben: das vorgezogene Bild wurde nicht vorab geholt. Für ein Hintergrundbild des ersten Abschnitts schreibt page.html.twig längst ein <link rel="preload" as="image"> (heroBackground). Für ein gewöhnliches <img> über der Falz fehlte das Gegenstück – der Browser fand es erst, wenn das Stylesheet geparst war, und diese Rundreise ging auf die Startseite von EB Solar (deren Startbild ein Bild und kein Hintergrund ist) direkt in die LCP-Zeit. Neu liefert PageController::heroImage() Quelle, Variantensatz und Größenangabe genau so, wie blocks/block.html.twig sie an das img schreibt (sonst lädt der Browser zweimal), und die Vorlage schreibt daraus die Vorabladung – aber nur, wenn nicht ohnehin schon ein Hintergrundbild vorgeholt wird.

Und eine Regel fürs Aufbauen: eine Einblendung auf dem größten Element über der Falz kostet fast eine Sekunde LCP (gemessen: 916 ms „element render delay“, mobil 92 statt 99). Das Startbild blendet deshalb nicht ein; Kleinzeile, Überschrift, Text und Knöpfe daneben schon.

Geprüft: npm run test:links gegen Port 8096: 17 Adressen in der Sitemap, 48 Seiten, 127 Adressen, kein toter Verweis. npm run test:browser (PHP_BINARY=C:/php84/php.exe): sechs Seiten-/Viewport-Prüfungen mit axe, Tastatur und ohne JavaScript bestanden, keine redaktionellen Befunde; Ladebudget der Startseite 14 Anfragen, 17 Abfragen. Lighthouse 13.4.1 (scripts/audit.mjs, je drei Läufe mobil und Desktop, Mediane) gegen den Produktionsmodus auf Port 8097 für /, /projekte, /solarcheck, /michelbach, /rheinstetten, /impressum, /datenschutz: Barrierefreiheit, Best Practices und SEO überall 100, Leistung 99–100, agentisches Browsing überall 100 außer /solarcheck mobil (98). Startseite LCP 2,1 s mobil / 0,8 s Desktop, CLS 0, TBT 0 ms; Berichte in reports/lighthouse-ebsolar/. Bildbetrachter der Galerie geöffnet und geblättert („1 von 4“), Beitragsnavigation stimmt mit der Reihenfolge der Vorlage überein (Michelbach ← Rheinstetten, → Au am Rhein), 404 und Solarcheck im Browser angesehen, Desktop und 375 px. PHP-Tests 581 grün (PHP 8.4), php-cs-fixer und Twig-Lint ohne Befund.

Stolperstein beim Messen: Im Produktionsmodus liegen die uebersetzten Twig-Vorlagen in var/cache/prod. Nach einer Aenderung an blocks/icon.html.twig warf jede Seite mit Pfeil dort 500 („Key ‚arrow-right‘ … does not exist“), waehrend der Entwicklungsserver auf 8096 laengst richtig lieferte. APP_ENV=prod … bin/console cache:clear raeumt das auf; wer im Produktionsmodus misst, muss das nach jeder Vorlagenaenderung tun.

Bekannt: /solarcheck mobil hat CLS 0,08 (agentisches Browsing 98). Das Formularskript blendet nach dem Laden alle Schritte bis auf den ersten aus – ohne Skript stehen sie untereinander, mit Skript springt die Seite einmal. Der Wert liegt unter dem Ziel von 0,1, aber die Bewegung ist sichtbar; sie gehört in den Kern (Schritte serverseitig zusammenfalten), nicht ins Aufbauskript.

Startseite nach der Installation: eine Baustellenseite statt einer Anleitung (16.09.2026)

Anlass: Umut: „Kannst du bitte für die Erstseite bzw. nach der Installation eine schöne Erstseite erstellen – Under Construction oder so, ohne Details mit ‚melde dich an‘ usw. Am besten auch noch Logo von KantSites: ‚Hier entsteht eine neue Webpräsenz mit KantSites‘.“

Was los war. DemoContent::starterPage() lieferte zwei Abschnitte mit Arbeitsanweisungen an die Betreiberin oder den Betreiber: „Melde dich an, öffne sie im Editor …“ und „Die nächsten Schritte“ mit Design, Menüs und Einstellungen. Das stand auf der einzigen Seite, die bis zum ersten Bearbeiten öffentlich erreichbar ist – gelesen hat es also der Besucher, nicht der Betreiber. Für den stehen dieselben Schritte längst auf dem letzten Installerbildschirm (templates/install/done.html.twig) und in der Verwaltung.

Neu. Eine Baustellenseite, die genau das sagt und sonst nichts: das KantSites-Zeichen (public/assets/kantsites-logo.svg, dieselbe Form wie das Favicon), die Kleinzeile „In Arbeit“ in der Akzentfarbe, als einzige H1 der Name der Website, darunter „Hier entsteht eine neue Webpräsenz.“, ein kurzer Trennstrich und die Zeile „Erstellt mit KantSites“. Der Abschnitt ist ein Flex-Container mit min-height: 70vh und einem Verlauf von @paper nach @mint, stellt den Inhalt also mittig auf den Bildschirm; die Farben sind Palettenverweise und folgen jedem Designwechsel. Alles sind gewöhnliche Bausteine mit gewöhnlichen Stilwerten – im Editor also vollständig änder- und löschbar. Die Seitenbeschreibung ist jetzt ein echter Satz statt „NOCH AUSZUFÜLLEN“, damit eine frisch installierte Website in Suchergebnissen nicht mit einem Platzhalter steht.

Unverändert: Kopf- und Fußbereich, die beiden Rechtsseiten als Gerüst mit markierten Pflichtangaben, Menüs.

Geprüft: tests/InstallFlowTest.php (Installation über die sechs Bildschirme, danach liefert / die neue Seite), php vendor/bin/phpunit 579 von 579. Im Browser angesehen: eine frisch migrierte Datenbank mit dem Inhalt einer Neuinstallation, Desktop und 375 px.

Fassung 1.21.0: Bildecken gehören dem Designbereich (16.09.2026)

Anlass: Umut zum Hero-Bild von DEEG: „Das sollte abgerundet sein, aber wurde von irgendwo gemacht, und die Ecken sehen komisch aus. Es gibt auch im Editor gar nicht Bildecken abrunden, oder?“

Was los war. .block-image{border-radius:12px} stand fest in enhancements.css – jedes Bild jeder Installation war gerundet, ohne dass ein Feld das gesagt hätte. Auf DEEG (eine durchgehend eckige Vorlage) fiel es auf, weil über dem gerundeten Bild eckige Elemente liegen: der dunkle Verlauf, das Schild, die blaue Aufruf-Fläche. An den Ecken schaute deshalb die eckige Fläche aus der Rundung heraus.

Behoben. Die Ecken sind jetzt eine Einstellung unter Design → Bildecken (theme.base.imageRadius, geprüft wie die Inhaltsbreite, ausgegeben als --k-image-radius). Vorgabe bleibt 12px, damit bestehende Websites gleich aussehen; 0px macht alle Bilder eckig. Für ein einzelnes Bild gab es die Einstellung schon immer – im Editor unter Design → Bild → Bildecken (imageRadius im Stilschema, für Bild, Galerie, Logoleiste und Beitragsliste); sie überschreibt den Wert der Website.

DEEG: imageRadius auf 0px, im Aufbauskript und in der Installation. Das Hero-Bild ist damit eckig wie die Vorlage, und die Ecken stimmen wieder.

Geprüft: neuer Test in tests/CmsTest.php: Vorgabe ist 12px, eine Seite mit Bild trägt --k-image-radius:12px, nach dem Speichern von 0px trägt sie --k-image-radius:0px, und „rund“ wird abgewiesen wie jede andere Nicht-Größenangabe.

Fassung 1.20.1: das kaufmännische Und, das bei jedem Speichern wuchs (16.09.2026)

Anlass: Umut: „Beim Startseitenbild wird der Text & umgewandelt, aber in der Ansicht nach dem Speichern wird & daraus.“

Ursache. RichText::sanitise() hat Text ohne ein einziges Tag als reinen Text behandelt und nur escaped. Was der Editor schickt, ist aber HTML – auch ohne Tag. Aus Freudenstadt &amp; Umgebung wurde so bei jedem Speichern eine Stufe mehr: &amp;amp;, &amp;amp;amp; … bis die Entity sichtbar auf der Seite stand. Auf kant-it.de war der Entwurf der Startseite schon bei drei Stufen. Derselbe Zweig hatte einen zweiten Fehler gleicher Art: nl2br() ließ den Zeilenumbruch hinter dem <br> stehen, und der nächste Durchlauf machte über breakLines() ein zweites <br> daraus – aus einem Absatz wurden zwei.

Behoben. Der Zweig dekodiert jetzt zuerst (html_entity_decode) und escaped danach, und er ersetzt den Umbruch, statt ihn zu ergänzen. Damit endet er bei derselben Darstellung wie der DOM-Zweig, und beide sind unveränderlich gegenüber wiederholtem Speichern.

Bestand. Der Konsolenbefehl bin/console cms:repair:entities räumt auf, was schon gespeichert ist: veröffentlichte Fassung und Entwurf jeder Seite sowie die Bereiche, mit Trockenlauf und --schreiben, jede Änderung gegen den Validator geprüft (Seiten wie beim Speichern, Bereiche ohne Überschriftspflicht). Als Befehl und nicht als Skript unter scripts/, weil dieser Ordner nicht ins Releasearchiv geht – eine Installation ohne Git hätte das Skript sonst nie gesehen. Ältere Fassungen bleiben Historie. Auf DEEG angewandt (eine Stelle im Entwurf der Startseite). Offen: kant-it.de – dort stehen zwei dreifach kodierte Stellen im Entwurf der Startseite; das Skript gehört nach dem Update dort einmal ausgeführt, sonst steht der Murks beim nächsten Veröffentlichen live.

Geprüft: tests/RepairEntitiesCommandTest.php (Trockenlauf ändert nichts; Schreiben entschachtelt drei Stufen und lässt ein richtiges &amp; stehen; ein zweiter Lauf findet nichts mehr) und ein neuer Test in tests/CmsTest.php: sechs Eingaben (Entity, nacktes Und, <, Zeilenumbruch, Markup, wörtliche Tags) dreimal hintereinander durch sanitise() – jedes Mal dasselbe Ergebnis; dazu eine Seite, die dreimal gespeichert und veröffentlicht wird und danach immer noch ein einziges &amp; trägt.

Nebenbei (DEEG): Das Favicon zeigte den mittigen Ausschnitt des Logos, also „EE“ aus dem Schriftzug. Neu ist ein eigenes quadratisches Zeichen: der Dachgiebel aus dem Logo, weiß auf Logo-Blau, gerendert aus logo-deeg.png von scripts/set-immobilien-deeg-favicon.php. Vier Entwürfe standen zur Wahl (Doppeldach, Giebel, Dach blau auf weiß, Buchstabe D), Umut hat den Giebel gewählt.

Baustein „Eigenes HTML“, DEEG-Angebotsseite (16.09.2026)

Anlass: Umut: „Als Baustein im Editor, dass man individuelles HTML einfügen kann … z. B. eine neue Angebotsseite bei DEEG, da soll ein iframe rein, im gleichen Stil wie das Impressum.“

Gebaut. Neuer Kernbaustein html („Eigenes HTML“, Dokumentschema v24): eine Eigenschaft, das Markup bis 20.000 Zeichen, unverändert ausgegeben. CustomCode::checkHtml() weist <script> und <iframe> mit dem Weg zum richtigen Ort ab; CustomCode::assertHtmlUnchanged() hält Änderungen auf die Administratorrolle fest – in EditorController (Speichern und Zurückholen einer Fassung) und in PartialController, wie beim eigenen Skript. Im Editor: Eintrag in der Gruppe „Seite und Daten“, mehrzeiliges Feld, schreibgeschützt ohne Administratorrolle. In site.css nur so viel Regel, dass fremdes Markup die Spalte nicht sprengt.

Nicht gebaut, weil vorhanden: der iframe. Der Baustein „Externer Inhalt“ (v14) macht das seit Langem – mit Einwilligung davor und dem Host in frame-src. Begründung in ADR 106.

DEEG. Die Seite /angebote gab es schon (im Editor angelegt, Starterabschnitt plus Rahmen mit Platzhaltertexten „test“). Sie ist jetzt im Stil der Rechtsseiten aufgebaut: Kleinzeile, große leichte Überschrift „Unsere Angebote“, Vorspann auf heller Fläche, darunter die IS24-Ergebnisliste (Portal 16200281, Höhe 1000) und eine Hinweiszeile zum Portal und zum Kontakt. „Immobilien“ in der Navigation und der Knopf im Angebote-Band der Startseite zeigen auf /angebote statt auf die alte Website. Aufbau per Skript: scripts/add-immobilien-deeg-angebote.php.

Einwilligung und Gestaltung (DEEG). Die Gruppe „Externe Inhalte“ ist eingeschaltet (consentBanner, consentGroups), der Balken trägt die Farben der Website (eckig, Logo-Blau als Akzent, Papier als Fläche). Vor der Zustimmung steht der Platzhalter, und der trägt über eigenes CSS am Block denselben Zuschnitt wie die Vorlage: eckiger Rahmen auf heller Fläche, linksbündig, blauer Knopf. Gestaltbar ist der Rahmen, nicht sein Inhalt – Abstände, Breite, Rand, Ecken und Höhe gehören dieser Website, die Darstellung der Liste gehört ImmobilienScout24; an fremdes Dokument im iframe kommt kein CSS heran, das trennt der Browser. Im Browser geprüft: Platzhalter, Zustimmung, geladene Liste.

Nicht mehr offen: Die Einwilligungsfrage war auf dieser Installation aus (consentBanner false, keine Gruppen). Das ist erledigt: Umut hat die Einwilligung eingeschaltet, ein fremder Rahmen lädt jetzt erst nach der Zustimmung.

Geprüft: phpunit 574 Tests grün, npm test 35 (neu: der Baustein startet leer, steht im Werkzeugkasten, hebt das Dokument auf v24). npm run test:editor um den ganzen Weg erweitert und bestanden: Baustein aus dem Werkzeugkasten einfügen, Markup eintippen, in der Vorschau sehen, veröffentlichen, auf der Website Zeichen für Zeichen wiederfinden – und <script> wie <iframe> werden mit ihrer Meldung abgewiesen (422, Meldung in der Leiste über dem Editor), ohne die veröffentlichte Seite anzufassen. Zwei neue PHP-Tests: Markup erscheint unverändert auf der veröffentlichten Seite und <script>, <iframe> sowie Schema v23 werden abgewiesen; HTML-Änderungen werden gegen den gespeicherten Stand geprüft.

Fassung 1.19.0 veröffentlicht (16.09.2026)

Anlass: Umut: „Ja committe das bitte und push mit neuem Release.“ Die Rücknahme der Designpakete geht als 1.19.0 heraus.

Dabei aufgefallen: Die Commits c8a8fa7 und 53fb713 (v1.18.0) hatten den Arbeitsstand dieser Sitzung schon mitgenommen – in v1.18.0 fehlen applyTheme() und plugins/nachtblau bereits, während die mitgelieferte Dokumentation Designpakete noch beschreibt. 1.19.0 bringt beides wieder zur Deckung. Der Code ändert sich zwischen 1.18.0 und 1.19.0 nur um die Rollenmatrix des Tests.

Geprüft: phpunit 572 Tests in einem Prozess grün (PHP 8.4), npm test 34, Twig-Lint 53 Dateien, php-cs-fixer ohne Befund, npm run test:editor bestanden (Bildschirm „Plugins“ ohne Designpaket-Knopf, Barrierefreiheitsprüfungen durch). DEEG-Startseite im Browser gesichtet: eigene Palette wieder da. Danach Commit, Tag v1.19.0, Push nach origin und Releasearchiv.

Designpakete in Plugins entfernt (16.09.2026)

Anlass: Umut auf der DEEG-Installation: „Ich habe das Designplugin aktiviert und nach dem Deaktivieren blieben die Farben.“ Nicht der Schalter war schuld, sondern „Designpaket übernehmen“ – der Knopf schreibt die Palette des Plugins endgültig in die Website. Entscheidung: die Funktion fällt weg, statt sie abzusichern (ADR 105).

Gemacht. DEEG-Design aus scripts/seed-immobilien-deeg.php wieder eingespielt (Logo-Blau, Anthrazit, Arial, eckige Buttons; Inhalte und Stilvorlagen waren nie betroffen). Aus dem Kern entfernt: PluginRegistry::themePackage() und applyTheme(), theme im Manifest und in der Pluginstruktur, Route admin_plugin_theme samt Theme-Abhängigkeit von PluginAdminController, der Knopf und die Designpaket-Zeile auf dem Bildschirm „Plugins“, der Ordner plugins/nachtblau. Ein Manifest braucht jetzt mindestens einen Baustein, eine Adresse oder eine Vorlage.

Geprüft: tests/PluginTest.php hat statt des Designpaket-Tests jetzt „Ein- und Ausschalten lässt das Design in Ruhe“ (Palette vorher und nachher gleich, Dokument unverändert); 8 Tests grün.

Fassung 1.18.0 veröffentlicht (16.09.2026)

Anlass: Umut: „Bau eine neue Version bitte, Commit, Push und Release.“ Der Stand aus DEEG-Nachbau, Formularschritten, Pluginsystem und den Rückständen der Gesamtpruefung geht als 1.18.0 heraus.

Vorher gefunden und behoben: Der komplette phpunit-Lauf in einem Prozess starb reproduzierbar nach 80 Sekunden mit „Maximum execution time exceeded“, an wechselnden Stellen. Ursache war nicht ein Test, sondern TaskWorkerSubscriber: set_time_limit() setzt die Grenze, es verlängert sie nicht. Auf einem Prozess ohne Grenze – Konsole, Testlauf, Hoster mit max_execution_time = 0 – machte set_time_limit($budget + 60) aus „unbegrenzt“ achtzig Sekunden, und der Prozess starb später irgendwo weit weg vom Aufgabenplan. Derselbe Fall stand im Takt der Verwaltung (TaskController::tick(), 120 Sekunden). Beide Stellen heben die Grenze jetzt nur an und senken sie nie. Einzeln liefen alle Testdateien immer durch, deshalb war das bisher nie aufgefallen.

Geprüft: neuer Test in tests/SchedulerTest.php („der Worker kürzt einen Prozess ohne Zeitgrenze nicht“): faellige Aufgabe läuft, max_execution_time bleibt 0. phpunit 572 Tests, 4768 Zusicherungen in einem Prozess grün (vorher Abbruch), npm test 34, Twig-Lint mit plugins/, php-cs-fixer. Danach Commit, Tag v1.18.0, Push nach origin und Releasearchiv.

Rückstände der Gesamtprüfung abgearbeitet (16.09.2026)

Anlass: Umut: „Bitte umsetzen“ zur Liste aus ADR 100. ADR 104 beschreibt jeden Punkt; hier die Kurzfassung.

  • Archivseiten laufen durch renderPage() (eigenes Dokument aus Kopfabschnitt und blätternder Beitragsliste): Kopf/Fuß mit Stilen, Sektionen, eigenes CSS, Bündel, Nach-oben, Einwilligung, Skripte. templates/archive.html.twig gelöscht.
  • Formulare, Beitragslisten, Brotkrumen in Bereichen und Sektionen werden aufgelöst; Meldungen eines abgewiesenen Formulars finden den Block über die Formularadresse. Skripte in Bereichen laufen als /assets/bereich/<id>.js.
  • Doppelte Block-IDs bei mehrfach verknüpfter Sektion: PartialRepository::expandRepeatedLinks() erzeugt Kopien mit eigenen Kennungen (Website und Editorvorschau).
  • Breakpoints überall 1024/767 (design.css neu gebaut, site.css, enhancements.css, preview.css, editor.css, Editortext).
  • Farben: color-scheme, Skip-Link, Aufruf-Button, Fehlerfarben, .tone-ink-Text aus der Palette; Theme::css() setzt --k-scheme und die Fehlerfarben nach Leuchtdichte des Papiers; Formularbutton dunkelt in die Tinte. Druckstile neu.
  • Betrieb: Sicherung/Wiederherstellung auf MySQL (mysqldump/mysql), Git-Updater sichert und rollt über Backup, Sperre var/update.lock für beide Wege, Archiv als Datei geladen, .htaccess zusammengeführt (HTTPS/HSTS bleiben an), var/update/ und alte Caches nach dem Update aufgeräumt, CI-MySQL-Job mit cms:health, cms:backup und phpunit.
  • SEO: Canonical mit ?seite=n und rel=prev/next bei blätternden Beitragslisten; Druckstile.
  • Editor: Vorschau mit eigenem CSS, Bereichs-/Sektionsstilen und Formularen; Kontaktdaten, Formular, verknüpfte Sektion und Embed direkt nach dem Einfügen gültig (leer = nichts gewählt); bereinigter Rich Text sofort im Editor (EditorHistory.adopt()).
  • Kleines: legal.label begrenzt, Docblocks in PageController gerückt.

Geprüft: neue tests/BacklogTest.php (7 Tests, 55 Zusicherungen: Archiv mit Fuß, Sektion, Nach-oben, Canonical/prev/next, Kategorie mit BreadcrumbList; blätternde Liste auf einer Seite; Formular im Fuß samt 422-Wiederanzeige; Sektion zweimal verknüpft; leere Bausteine; dunkle Palette; Sperre und .htaccess-Zusammenführung). Bestehende Tests angepasst: leerer Kontaktblock ist gültig, Schrittfolge des Updaters mit „Sperre“, ReleaseOnDisk kennt toFile(). Komplette Kette grün: phpunit 571 Tests (PHP 8.4), npm test 34, Twig-Lint mit plugins/, php-cs-fixer auf allen geänderten Dateien, npm run test:editor bestanden (Vorschau mit Formularen und eigenem CSS), DEEG npm run test:browser bestanden (Startseite 168 KB, 11 Anfragen – die vier Optionsbilder des Preisfinders kamen dazu), Lighthouse / Desktop überall 100, mobil Leistung 99 (die vier Piktogramme laden mit; Rest 100). Nicht committet.

Arbeitspaket H zu Ende: Container, Einstellungen, Adressen, Vorlagen, Designpakete (16.09.2026)

Anlass: Umut: „Dann mach H.“ Zweiter Teil des Pluginsystems (ADR 103). Grundentscheidung: ein Plugin bleibt Manifest plus Twig, ohne PHP – deshalb keine Hooks und keine eigenen Tabellen, dafür alles, was sich ohne Installationscode sicher übergeben lässt.

Gebaut. PluginRegistry liest jetzt auch settings (Schema wie Eigenschaften, gespeichert in der neuen Spalte plugin.settings, Migration 20260916000100), container/children am Baustein, routes (Pfad wie eine Seite, Vorlage, Antwortart html/json/text/xml, Kernadressen gesperrt), presets (Bausteinlisten bis 64 KiB) und theme (Palette, Schriften, Grundwerte, Buttons). block.html.twig rendert die Kinder eines Containers selbst und reicht sie der Vorlage als children, dazu settings; DocumentValidator weist Kinder in Nicht-Containern und nicht erlaubte Kinder ab. PageController::page() beantwortet eine Plugin-Adresse, wenn keine Seite den Pfad hat (vor Weiterleitung und 404), mit Einstellungen und gesäuberten Abfrageparametern, Schutzkopfzeilen, ohne Skript, außer html mit noindex. Editor: registerPlugins(list, presets) kennt Container und erlaubte Kinder (canContain), insertPreset('plugin:…') fügt Vorlagen mit frischen Kennungen ein, der Werkzeugkasten zeigt sie hinter den eingebauten. Bildschirm „Plugins“: Einstellungsformular je aktivem Plugin, Adressen verlinkt, „Designpaket übernehmen“ (Theme::save(), Inhalte bleiben), beide Wege in der Rollenmatrix. Referenz: plugins/vergleich 1.1.0 (Einstellungen, Vergleichsgalerie als Container nur für Bildvergleiche, Vorlage „Vorher-Nachher-Abschnitt“, Adresse /vergleich/daten), neu plugins/nachtblau als reines Designpaket. Alle fünf lokalen Installationen migriert. docs/PLUGINS.md beschreibt das vollständige Manifest.

Geprüft: tests/PluginTest.php jetzt 8 Tests: Einstellungen (Vorgaben, Umwandlung aus dem Formular, Abweisung, Wirkung in Vorlage und Adresse, 404 bei ausgeschaltetem Plugin), Container (Kinder gerendert, Stylesheet einmal, fremdes Kind und Kinder im Nicht-Container abgewiesen, Editor-Definitionen und Vorlagen), Designpaket (übernommen, Buttons, Dokument unverändert, nur eingeschaltet, Fehler ohne Paket). editor-model.test.mjs prüft registrierte Container, Vorgaben, canContain und Vorlagen mit frischen Kennungen (34 grün). EditorSecurityTest mit den neuen Routen grün. npm run test:editor um Einstellungen speichern, die Adresse /vergleich/daten, die Plugin-Vorlage im Werkzeugkasten und den Container erweitert (Bildvergleich landet in der Galerie, ein Text daneben) und bestanden; ein Lauf davor blieb beim Neuladen des Editors bei „Wird geladen“ hängen, der nächste lief ohne Änderung durch – der Check protokolliert jetzt jede Konsolenfehlermeldung, damit so etwas beim nächsten Mal einen Grund hat. Komplette Kette grün: phpunit 565 Tests (PHP 8.4), npm test 34, Twig-Lint mit plugins/, php-cs-fixer auf allen neuen und geänderten Dateien. Nicht committet.

Pluginsystem, erste Fassung – Manifest, Schalter, geprüfte Bausteine, Referenzplugin „Bildvergleich“ (15.09.2026)

Anlass: Umut: „Plugin hätte ich gerne aber umgesetzt. Also Möglichkeit. … Dann Plugin so wie du es gesagt hast, hört sich gut an.“ Arbeitspaket H, erster Teil (ADR 102).

Gebaut. src/Plugin/PluginRegistry.php liest plugins/<name>/plugin.json (Titel, Fassung, requires, Bausteine mit Beschriftung, Symbol, Vorlage, Stylesheet, Skript und Eigenschaftenschema mit sieben Arten), meldet kaputte Manifeste mit Grund und hält den Schalter in der neuen Tabelle plugin (Migration 20260915000100, in TestSchema). DocumentValidator prüft die Eigenschaften eines Plugin-Bausteins beim Schreiben gegen das Schema (validate(..., writing: true) an allen Schreibstellen), beim Lesen nicht – ein später eingeschaltetes Plugin macht keine Seite unlesbar. templates/blocks/block.html.twig rendert Bausteine aktiver Plugins über @plugins/... mit den erneut geprüften props (src/Twig/PluginExtension.php: kant_plugin_block, kant_plugin_props, kant_plugin_media); passt etwas nicht, bleibt der Kommentar. PluginAssetController liefert nur Dateien aktiver Plugins unter assets/ mit erlaubter Endung; PageController verlinkt sie je Seite und öffnet die Richtlinie für ein Skript nur dann. Design-Tokens gelten jetzt auch für Plugin-Typen. Editor: der Dokumentaufruf liefert die Definitionen (plugins), registerPlugins() in editor-model.js trägt sie ein (Gruppe „Erweiterungen“, Vorgaben, Schema v4), renderPluginFields() in editor.js baut das Panel aus dem Schema, ein Bild über die Mediathek; die Vorschau lädt das Stylesheet, kein Skript. Verwaltung: Bildschirm „Plugins“ (Administrator) mit Ein/Aus, CSRF, in Navigation und Rollenmatrix. Release/Update: plugins/ im Archiv und beim Archiv-Update Datei für Datei übernommen. Entwicklerdoku docs/PLUGINS.md.

Referenzplugin plugins/vergleich: Baustein vergleich.bildvergleich – zwei Bilder aus der Mediathek, ein Regler dazwischen (natives Bereichsfeld, Skript setzt nur --pos über das CSSOM), Beschriftungen, Startposition, Ecken. Ohne Skript steht der Vergleich bei der Startposition.

Geprüft: tests/PluginTest.php (5 Tests, 50 Zusicherungen): Manifest lesen und schalten (Schalter überlebt eine neue Registry), drei kaputte Manifeste mit Grund und ohne Schalter, Eigenschaftenprüfung nur bei aktivem Plugin und nur beim Schreiben, Rendering mit Stylesheet/Skript/CSP gegen Kommentar bei ausgeschaltetem Plugin, Asset-Route (Typ, immutable, 404 für Manifest, Vorlagen, .. und ausgeschaltete Plugins), Design-Tokens und eigenes CSS am Plugin-Baustein. EditorSecurityTest mit den Plugin-Routen grün (38 Tests), npm test 33 grün, Twig-Lint aller Vorlagen einschließlich plugins/. Die vier lokalen Installationen sind migriert. npm run test:editor um ein Plugin-Szenario erweitert und bestanden: Bildschirm „Plugins“ mit axe (die Überschriftenfolge dort war zuerst falsch, h3 unter h1), Einschalten, Baustein aus dem Werkzeugkasten, Panel aus dem Schema, zwei Bilder über die Mediathek, Vorschau mit Stylesheet und ohne Skript, Speichern und Veröffentlichen, Website mit Skript und script-src 'self', Ausschalten lässt den Baustein im Dokument und zeigt den Kommentar (reports/editor/plugins.png, plugin-block.png). Danach die komplette Kette: phpunit zuerst mit drei Befunden – der feste Twig-Pfad paths in twig.yaml brach jede Installation ohne Ordner plugins/ (Installer-Tests), und die Vollständigkeitsprüfung des Archiv-Updaters kannte plugins/ nicht. Jetzt registriert PluginExtension den Namensraum @plugins zur Laufzeit nur, wenn der Ordner existiert, und der Test führt plugins/ als Datei-für-Datei-Ordner wie public/. Danach phpunit vollständig grün (562 Tests, 4664 Zusicherungen, PHP 8.4), Twig-Lint mit plugins/, php-cs-fixer auf allen neuen und geänderten Dateien.

Nicht gebaut: Container-Bausteine, eigene Routen und Einstellungen, Tabellen, Hooks, Ressourcen in der Verwaltung, Templates. Kein Sandkasten – Plugin-Vorlagen laufen mit den Rechten der Anwendung, so steht es auf dem Bildschirm. Nicht committet.

Formulare mit Schritten, Bedingungen und Zusammenfassung – der DEEG-Preisfinder (15.09.2026)

Anlass: Umut: „Plugins funktionieren noch nicht? Was fehlt denn? Ich hätte gerne kein Kontaktformular weiteres, sondern so ein ausgebautes Form.“ Dann: „Okay, dann erweitere den Kern.“ Gemeint ist der Preisfinder der laufenden DEEG-Website: Objektart wählen, Eckdaten je Objektart, Kontakt, Zusammenfassung.

Kern (ADR 101): Drei neue Feldtypen – Zahl (Unter-/Obergrenze, Einheit), Auswahl als Optionsfelder, Mehrfachauswahl. Ein Feld „Neuer Schritt“ eröffnet einen Abschnitt (Beschriftung = Titel, Hilfetext = Einleitung). Jedes Feld und jeder Schritt kann eine Bedingung tragen: gilt nur, wenn ein früheres Auswahlfeld einen der genannten Werte hat. Einstellung „Zusammenfassung vor dem Absenden“. Höchstens 40 statt 25 Felder. Der Server prüft die Bedingungen selbst (FormRepository::check()): ein Feld, das nicht galt, wird weder verlangt noch gespeichert, auch wenn der Browser einen Wert schickt; beim Speichern der Definition muss jede Bedingung auf ein früheres Auswahlfeld mit passenden Optionen zeigen. Bestehende Formulare bleiben ohne Migration gültig (present() ergänzt die Schlüssel).

Öffentlich: templates/blocks/form.html.twig rendert Schritte als fieldset/legend, Gruppen für Options- und Mehrfachauswahl, type="number" mit Grenzen und Einheit, Bedingungen als data-show-if/data-show-values. Ohne JavaScript steht das Formular vollständig auf der Seite. Neu public/assets/form-steps.js (nur auf Seiten, deren Formular es braucht, FormRepository::needsScript(); die CSP erlaubt es dann): Fortschritt, „Zurück“/„Weiter“ mit Browserprüfung je Schritt, Felder hinter nicht zutreffenden Bedingungen deaktiviert statt nur versteckt, Zusammenfassung als letzter Schritt mit „Ändern“, nach einer Serverablehnung der erste Schritt mit Fehler. site.css: Stile für Schritte, Fortschritt, Gruppen, Zahl mit Einheit, Zusammenfassung; .block-form [hidden] schlägt jetzt display:grid/inline-flex. E-Mail: Schritte als Zwischenüberschrift, Felder, die nicht galten, bleiben weg.

Bildauswahl (Nachtrag): Umut meinte mit „Upload“ eine Bildauswahl wie im Solar-Rechner von Elektro Bernhard (Bildkarten statt Kästchen). Optionsfelder und Mehrfachauswahl tragen jetzt je Option ein Bild aus der Mediathek; sobald eine Option eines hat, wird die Auswahl auf der Website zu anklickbaren Karten mit Bild und Text, das Kästchen bleibt darin für Tastatur und Vorlesesoftware. Im DEEG-Preisfinder hat die Objektart vier Piktogramme (var/immobilien-deeg-assets/option-*.png, mit GD gezeichnete Platzhalter, in der Mediathek gegen Fotos tauschbar). Test testAChoiceWithPicturesBecomesClickableCards, dazu zwei abgelehnte Zuordnungen.

Verwaltung: Feldkarte mit Untergrenze/Obergrenze/Einheit (bei Zahl), Optionen bei Liste/Optionsfeldern/Mehrfachauswahl, „Bild je Option“ als Liste der Mediathek, Bedingung („Nur zeigen, wenn das Feld … einen dieser Werte hat“); Zustellung mit Schalter „Zusammenfassung vor dem Absenden zeigen“; Vorschau zeigt Schritte, Optionsfelder, Kästchen, Einheit und Bedingungen. Alles ohne Skript bedienbar.

DEEG: Das Formular bewertung im Aufbauskript ist jetzt der Preisfinder: Schritt 1 Objektart (Wohnung, Haus, Mehrfamilienhaus, Grundstück) und Anlass (Verkaufen, Vermieten); Schritt 2 Eckdaten je Objektart – Wohnfläche, Grundstücksfläche, Zimmer, Wohneinheiten, Baujahr, Zustand, Einbauküche, Aufzug, Parkmöglichkeit, Bebaubarkeit, jeweils hinter Bedingungen; Schritt 3 Lage und Kontakt; dann die Zusammenfassung. Die Feldliste folgt dem Preisfinder von immobiliendeeg.de (Wohnung-Seite, 15.09.2026). Das Kontaktformular bleibt, wie es war.

Geprüft: neue tests/FormStepsTest.php (5 Tests, 75 Zusicherungen): Normalisierung beim Speichern, zehn abgelehnte Definitionen mit Begründung, Bedingungen in check() (Grundstück ohne Wohnfläche, Wohnung mit Pflicht, Zahl mit Komma, Grenzen, fremde Optionen), Rendering samt Skript und CSP (und ein einfaches Formular weiter ohne Skript, script-src 'none'), POST mit Speicherung nur der geltenden Felder und 422-Wiederanzeige mit erhaltenen Kreuzen. Im Browser (Port 8094): drei Schritte durchgespielt, Felder je Objektart, Zusammenfassung mit allen Werten, Absenden mit Erfolgsmeldung, Einsendung in form_submission mit leeren Feldern für alles, was nicht galt; Grundstück zeigt nur zwei Eckdatenfelder, „Weiter“ hält bei leerem Pflichtfeld. Komplette Kette: phpunit 556 grün (PHP 8.4), npm test 33 grün, npm run test:editor vollständig bestanden (darunter die axe-Prüfung der neuen Formularverwaltung), Twig-Lint ohne Befund, php-cs-fixer auf den geänderten Dateien (die vier Befunde in install.php und den drei älteren Aufbauskripten sind vorbestehend). DEEG nach dem Umbau: npm run test:browser bestanden (Startseite jetzt 2 Skripte – Einblendungen und Formular –, 161 KB), Lighthouse / mobil und Desktop weiterhin überall 100.

Browsercheck angepasst: scripts/browser-check.mjs erlaubte bisher höchstens ein Skript je Seite. Das war schon vor dem Formularskript zu eng – Slider, Reiter, Einblendungen, Bildbetrachter und Nach-oben sind je eine eigene Datei –, fiel aber erst jetzt auf, weil keine Referenzseite zwei davon lud. Neu: höchstens drei Skripte, alle aus /assets/ des eigenen Ursprungs, und weiterhin keins, wenn die Richtlinie keins erlaubt.

Nicht gebaut: Bedingungen auf Zahlenbereiche oder mehrere Felder, Dateiuploads, Schritt-für-Schritt ohne JavaScript, Formulare in der Editorvorschau. Nicht committet.

DEEG Immobilien & Hausverwaltung nachgebaut, Anfrage als Formular (15.09.2026)

Anlass: Umut lieferte Immobilien-DEEG-Website.zip (Onepager für die Deeg Hausverwaltung und Immobilien GmbH, Freudenstadt) und wollte ihn „nach dem Prinzip wie die anderen Seiten mit Kontaktformular“ aufbauen; dazu die Frage, ob die Bewertungsanfrage ein eigenes Plugin braucht.

Antwort auf die Plugin-Frage: Nein. Ein Plugin ist in diesem CMS ein Baustein mit Namensraum, den der Kern aufbewahrt, aber nicht rendert (ADR zum Erhaltungsvertrag, ADR 049); eine Laufzeit dafür gibt es erst mit Phase 4. Die Anfrage ist deshalb das Formular bewertung (Auswahl Wohnung/Haus/Mehrfamilienhaus/Grundstück – die vier Objektarten des Preisfinders der laufenden Website –, Straße und Ort, Name, E-Mail, Telefon, Hinweise, Einwilligung), der Kontakt das Formular kontakt (mit Auswahl Verkauf/Vermietung/Verwaltung). Beide verschicken serverseitig und liegen unter Formulare in der Verwaltung; die Vorlage verlinkte dafür noch auf www.immobiliendeeg.de.

Nachbau (scripts/seed-immobilien-deeg.php, scripts/reset-immobilien-deeg.sh). Fünfte Installation var/immobilien-deeg.db, var/immobilien-deeg-media, Bildquellen var/immobilien-deeg-assets (Logo, zwei Architekturfotos aus dem Paket), Startkonfigurationen immobilien-deeg (Port 8094) und immobilien-deeg-prod (Port 8095, Produktionsmodus zum Messen). Startseite mit Hero (Foto, Schild, Bildzeile, blauer Aufruf zur Bewertung), Leistungsleiste, drei Leistungskarten mit Akkordeon, blauem Bewertungsabschnitt mit Formularkarte, Über uns mit „SEIT 1994“, Angebotsband (Link auf www.immobiliendeeg.de/immobilien/, die Immobilien-Datenbank ist nicht Teil des Pakets), Kontakt mit Telefon, E-Mail, Adresse, Bürozeiten und Formularkarte; Kopf mit heller Leiste, Logo, Menü und blauem Knopf „Immobilie bewerten“; Fuß mit Logo, Rechtszeile und Agenturlink. Impressum mit Register und Vertretung vom Impressum der laufenden Website (HRB 430747, Amtsgericht Stuttgart, Furkan Kurt); USt-ID, Aufsicht nach § 34c GewO und Bildlizenz des übernommenen Fotos als „NOCH ZU PRÜFEN“. Datenschutz als eigene Fassung ohne Cookies und Fremddienste. Schrift Arial wie die Vorlage, nichts zu holen.

Für 100 geändert, nicht für die Optik: Blau als Fläche mit weißer Schrift trägt #0076b6 (4,9:1 statt 4,2:1 beim Logo-Blau), kleine blaue Schrift auf Weiß #006aa3; das Logo-Blau #0081c7 bleibt für Linien und große Überschriften. Der Button-Baustein hängt immer „↗“ an, die Vorlage kommt ausdrücklich ohne Pfeile aus – am Button & span{display:none}. Die Copyright-Zeile trägt ihre Farbe selbst, weil der Fuß kleinen Text sonst auf 3,8:1 aufhellt.

Browsercheck angepasst (scripts/browser-check.mjs): Vor Überlauf- und axe-Prüfung spielt das Skript alle laufenden Einblendungen zu Ende (document.getAnimations().forEach(a => a.finish())). Vorher hing das Ergebnis vom Zeitpunkt ab: mitten in einer Einblendung rechnete axe den Kontrast gegen die halb durchsichtige Farbe (drei Scheinbefunde am Hero), und ein von rechts hereingleitender Block zählte als horizontaler Überlauf.

Geprüft: npm run test:links gegen Port 8094: 3 Seiten, 14 Adressen, kein toter Verweis. npm run test:browser (PHP_BINARY=C:/php84/php.exe): sechs Seiten-/Viewport-Prüfungen mit axe, Tastatur, ohne JavaScript bestanden, keine redaktionellen Befunde; Ladebudget der Startseite 157 KB, 6 Anfragen, 11 Abfragen. Lighthouse 13.4.1 (scripts/audit.mjs, je drei Läufe Mobil und Desktop, Mediane) gegen den Produktionsmodus auf Port 8095 für /, /impressum, /datenschutz: Leistung, Barrierefreiheit, Best Practices, SEO und agentisches Browsing überall 100 (Startseite LCP 1,8 s mobil / 0,5 s Desktop, CLS 0, TBT 0 ms); Berichte in reports/lighthouse-immobilien-deeg/. Kontaktformular einmal abgeschickt: Erfolgsmeldung, Eingang in form_submission. Screenshots in 1440 und 390 px gegen die Vorlage verglichen. php-cs-fixer auf dem Skript ohne Befund.

Nicht geprüft: der Editor auf den DEEG-Dokumenten im Browser, E-Mail-Zustellung (lokal sendmail://default, der Eingang bleibt trotzdem gespeichert), Firefox/Safari, ein echtes Touchgerät, das Hosting. Nicht committet.

1.17.0 veröffentlicht (15.09.2026)

Tag v1.17.0 (Commit d07154e), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.17.0 – Adresse mit oder ohne www aus SITE_ORIGIN, Rollen wie die Rollenhilfe sie verspricht, Befunde der Gesamtprüfung (ADR 099, ADR 100).

Gebaut aus einem sauberen Checkout des Tags (git worktree add var/release-source-1.17.0 v1.17.0, php scripts/build-release.php darin): 3555 Dateien im Archiv, 4,7 MB.

Anhänge: kantsites-1.17.0.zip (SHA-256 d421654cd6803e3a683fbbd893db22c005e96261da4f64892c4ae1bb868557f6), .sha256, install.php mit eingetragener Prüfsumme; hochgeladen mit var/publish-release-1.17.0.py, erst Entwurf, dann veröffentlicht. Die Releasenotes (var/release-notes-1.17.0.md) nennen die Rollenänderung als ersten Punkt vor dem Update.

Geprüft: HTTP-Archiv-Update 1.16.1 → 1.17.0 ok (Update 302, System 200, Abschluss 302, öffentliche Seite 200; scripts/archive-update-check.mjs, aus dem Hauptrepository heraus); nach der Veröffentlichung ohne Anmeldung alle drei Anhänge bytegleich, latest = v1.17.0. Die Prüfungen des Codes stehen im Eintrag darunter.

Nächster Schritt: auf web.kant-it.de und dem Drews-Hosting über System → Update aus dem Archiv einspielen (keine Migration), danach unter System die eingetragene Adresse prüfen (SITE_ORIGIN mit oder ohne www, wie die Website heißen soll) und die Rollen der vorhandenen Konten unter Benutzer durchsehen – Redakteure sehen Design, Einstellungen, Bereiche, Menüs und Formulare nicht mehr.

1.17.0 – Gesamtprüfung, Adresse mit oder ohne www, Rollen wie versprochen (15.09.2026)

Anlass: Umut: „Komplette Analyse – Bugs, Durchgängigkeit, ob alles sauber ist; was man besser machen kann; und: die URL soll aus env kommen, www oder ohne, die Seite soll darauf reagieren.“ Nachgeschoben: „prüfst du auch, ob man ohne Admin etwas verändern kann?“

Prüfung. Sechs getrennte Durchsichten: Backend (Controller, Content, Security, Twig), Betrieb (Ops, Commands, Subscriber, Config, Installer, CI), Editor (JS, Admin-Templates, Modelltests), öffentliche Ausgabe (Templates, CSS, Skripte, SEO, CSP, Einwilligung), Dokumentation und Repository (README, docs, Skripte, Ports, Versionen, git-Hygiene) sowie eine Routenmatrix aller 63 Verwaltungs- und 15 öffentlichen Routen mit Firewall, Rolle, CSRF und Testabdeckung. Vorab: phpunit 512 grün, npm test 33 grün, Twig/YAML/Container-Lint ohne Befund, php-cs-fixer eine vorbestehende Zeile in CmsTest. Repository-Hygiene in Ordnung (313 Dateien, keine Geheimnisse, var/, reports/, .env ignoriert, VERSION = Tag = STATUS).

Antwort auf die Rollenfrage: Ohne Anmeldung kann niemand Inhalte, Einstellungen oder Konten ändern – alle Verwaltungsrouten liegen hinter der Firewall, jede schreibende prüft Rolle und CSRF-Token. Zwei echte Lücken gab es trotzdem, beide behoben: (1) Der Installer öffnete sich wieder, sobald die Datenbank nicht antwortete (Wartung beim Hoster, gedrehtes Passwort) – dort hätte jeder eine eigene Datenbank samt Administratorkonto eintragen können. (2) Die Rolle Redakteur durfte Design, Einstellungen (Startseite, robotsExtra, Turnstile, Einwilligung), Bereiche, Menüs und Formulare samt Empfängeradresse live ändern, entgegen der Rollenhilfe. ADR 100.

www / SITE_ORIGIN (ADR 099): CanonicalHostSubscriber leitet die jeweils andere Schreibweise mit 301 auf die eingetragene Adresse um (Pfad und Abfrage bleiben, Schema und Port aus SITE_ORIGIN, auch für die Verwaltung); fremde Hosts, IP-Adressen und localhost bleiben erreichbar, nur GET/HEAD, kein HTTPS-Zwang. Neu php bin/console cms:site:origin <adresse>; der Bildschirm „System“ zeigt Adresse, Quelle und einen Hinweis bei abweichendem Host. Der www-Block in public/.htaccess ist weg, .env.example erklärt die Einstellung, LIVEGANG Punkt 3 und README angepasst.

Behoben (Auswahl, alles mit Test): Sitzungscookie folgt ADMIN_PATH (Anmeldung nach cms:admin:path war unmöglich); %env(resolve:)% gegen prozentkodierte Passwörter durch projectdir:-Prozessor ersetzt; Einwilligung nur von dieser Website und kein /\-Rücksprung; JSON-LD mit JSON_HEX_TAG; Komma im eigenen CSS; Skriptprüfung beim Wiederherstellen einer Fassung; keine Unterseite unter einer Seite im Papierkorb; geplante Veröffentlichung blockiert bei einem Fehler nicht die anderen; Archiv escaped alte Bereiche; Feed-Zeitzone UTC; Sitemap-lastmod aus der Fassung; Vollbild in Embeds; Tabs/Bildbetrachter in Bereichen und Sektionen; Schatten bei Überfahren/Scrollen erbt Form; Download nur über HTTPS-Weiterleitungen; Composer/Git-Umgebung und PHP-Kommandozeile im Befehlsläufer; Updater fängt jeden Fehler. Editor: Schemafassung für Menü/Beitragsliste/Slider, gesperrte Design-Knöpfe bleiben gesperrt, Ausrücken/Baum-Zug mit Regeln, kein leerer Verlaufsschritt bei Ablehnung, Bild auf Akkordeon meldet sich, Klartext unter v12, Zahlenfelder ab 0, Farbwähler ohne onComplete. Docs: .env.local-Reste, motion/archive-update als npm-Skripte, README-Einleitung und dritte Referenzwebsite, UPDATE.md, install.php erkennt .env, Release-Builder ohne LICENSE.

Geprüft: phpunit vollständig mit PHP 8.4 grün (neu: CanonicalHostTest 24 Fälle, SiteOriginCommandTest, ProjectDirEnvProcessorTest, Rollen-Matrix und Cookie-Pfad in EditorSecurityTest, Installer-Fälle in InstallationTest, Systembildschirm, Tabs im Fuß, Sitemap-lastmod); npm test 33 grün (Fassungstabelle angepasst); Twig/YAML/Container-Lint ohne Befund; php-cs-fixer ohne Befund; cms:health gegen die Entwicklungsdatenbank mit dem neuen Prozessor OK; npm run test:editor (Playwright + axe, PHP_BINARY=C:/php84/php.exe) vollständig bestanden – ein erster Lauf scheiterte an einer seit 1.16.1 veralteten Zusicherung des Skripts („Spalte in der Sektion weiterhin gesperrt“), die jetzt die erlaubte Spalte erwartet. Nicht geprüft: Firefox/Safari, ein echtes Touchgerät, das Hosting, ein echter MySQL-Server.

Rückstand (aus der Prüfung, nicht umgesetzt): Archivseiten auf die Aufbaukette der Seiten heben (Kopf/Fuß-Stile, Schriften, Sektionen, Nach-oben); Formulare, Brotkrumen und Beitragslisten in Bereichen/Sektionen sind dort tot; doppelte Block-IDs bei mehrfach verknüpfter Sektion; Breakpoints 650 vs. 767 px vereinheitlichen; feste Farbwerte (.tone-ink, Skip-Link, CTA-Button, Fehlerrot, color-scheme) bei dunklen Paletten; MySQL ohne Sicherung, Archiv-Update und Rollback; Archiv-Update überschreibt .htaccess (HTTPS-Schalter); Release-Caches wachsen; Rollback-Kopie des Git-Updaters ohne Sperre; Archiv komplett im Speicher; Canonical/rel=prev/next blätternder Beitragslisten; Druckstile; Editor-Vorschau ohne eigenes CSS/Bereichsstile/Formulare; Skripte in Bereichen laufen nie; vier Blocktypen direkt nach dem Einfügen ungültig (Kontakt, Formular, Sektion, Embed); Rich-Text-Normalisierung erst nach Neuladen sichtbar; legal.label unvalidiert; verrutschte Docblocks; CI-mysql-Job ohne Tests; Ports 8090/8092 in QUALITY/STATUS sind Messserver, nicht die Startkonfigurationen.

Veröffentlicht als 1.17.0 (kein neues Dokumentschema, keine Migration; bestehende Redakteurskonten verlieren Design, Einstellungen, Bereiche, Menüs und Formulare – siehe Eintrag oben). Nächster Schritt: SITE_ORIGIN auf web.kant-it.de und dem Drews-Hosting prüfen und die HTTPS-Zeilen in .htaccess einschalten.

1.16.1 veröffentlicht (15.09.2026)

Tag v1.16.1 (Commit 937344e), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.16.1 – einzelne Spalte in einer Sektion, Ausrichtung für Buttons, breite und volle Abschnitte gegen die Design-Inhaltsbreite, Nach-oben-Knopf in einer Landmarke, Cormorant Garamond lokal.

Anlass: Die neu aufgebaute Drews-Datenbank trägt das Token „Ausrichtung“ an Buttons; unter dem auf dem Hosting laufenden 1.16.0 lehnte der Validator diese Dokumente beim Rendern ab (500 auf jeder öffentlichen Seite, Verwaltung lief). Das Release bringt Code und Datenbank wieder zusammen.

Gebaut aus einem sauberen Checkout des Tags (git worktree add var/release-source-1.16.1 v1.16.1, php scripts/build-release.php darin): 3552 Dateien im Archiv, 4,7 MB.

Anhänge: kantsites-1.16.1.zip (SHA-256 2922027abb648839b89962351aa3dbeaa9e354dd985c94a7a2efddabb86d6731), .sha256, install.php mit eingetragener Prüfsumme; hochgeladen mit var/publish-release-1.16.1.py, erst Entwurf, dann veröffentlicht.

Geprüft: HTTP-Archiv-Update 1.16.0 → 1.16.1 ok (Update 302, System 200, Abschluss 302, öffentliche Seite 200; scripts/archive-update-check.mjs); nach der Veröffentlichung ohne Anmeldung alle drei Anhänge bytegleich, latest = v1.16.1. Die Prüfungen des Codes stehen in den beiden Einträgen darunter.

Nächster Schritt: auf dem Drews-Hosting über System → Update aus dem Archiv einspielen (keine Migration), danach var/cache/prod prüfen; die kopierte Datenbank kann bleiben.

Einzelne Spalte in einer Sektion, Buttons ausrichten (15.09.2026)

Anlass: Umut konnte im Editor keine Spalte direkt in eine Sektion legen (ausgegraut) – so zentriert er einen Button –, und der Button „Alle Mitteilungen“ auf der Drews-Startseite stand links, ohne eine Option dafür.

Editor (editor-model.js): REQUIRE.column ist jetzt ['row', 'section']. Eine einzelne Spalte darf direkt in einer Sektion stehen (schmaler Inhalt, zentrierte Elemente), in einer Spalte oder lose im Dokument weiter nicht. Test angepasst.

Buttons (design-tokens.json, site.css, design.css neu gebaut): Das Design-Token „Ausrichtung“ gilt jetzt auch für Buttons. Weil ein Button inline-flex ist, zentriert ihn margin:auto nicht; site.css macht ihn bei align-center/-right zum Block mit eigener Breite (display:flex;width:fit-content;margin-inline:auto, dazu justify-self für Raster), je Gerät an den Breakpoints von design.css (1024/650 px). Das Drews-Aufbauskript nutzt das Token für alle mittigen Buttons statt der wirkungslosen auto-Ränder.

Geprüft: npm test 33 grün (darunter der geänderte Einfügetest), PHP-Tests zu Design-Tokens 11 grün; Drews-Startseite nach Neuaufbau: „Alle Mitteilungen“ und „Alle Projekte ansehen“ mittig, Aufruf-Buttons mittig, auf 1440 und 390 px. Unveröffentlicht, gehört mit den beiden Korrekturen darunter in 1.16.1.

Ihr Malerservice Drews nachgebaut, 15.09.2026

Anlass: Umut wollte maler-drews.com (Flatsome-Auftritt, Malermeister Axel Drews, Gernsbach) nach dem bekannten Prinzip in eigener Installation nachbauen – exakt im Viewer, mit Lighthouse, axe und maschinenlesbaren Ausgaben auf 100.

Vorlage ausgelesen, nicht geschätzt. Alle 9 Seiten und 31 Beiträge der laufenden Website als HTML geholt, Struktur, eigenes CSS (malerdrews-*, drews-*), Abschnittsfarben und Inline-Werte extrahiert, Screenshots in 1440 und 390 px als Vergleich. Befund: Lato + Cormorant Garamond 700, Gold #c6a36b, Creme #f7f5f2, dunkle Bänder #232323/#111/#0a0a0a, Kopf 90 px weiß mit Versalmenü, Startbild als Slider mit drei Folien und goldener Welle, Kartenraster aus Bild und Verlauf, Zickzackliste der Projekte, Zeitleiste der Mitteilungen. /about-us und /innenaustattung sind auf der Vorlage Lorem-ipsum-Reste, /little-greene ein 404.

Nachbau (scripts/seed-maler-drews.php, scripts/seed-maler-drews-inhalte.php, scripts/reset-maler-drews.sh). Eigene Installation var/maler-drews.db, var/maler-drews-media, Bildquellen var/maler-drews-assets (139 Bilder, auf 2400 px verkleinert, Zuordnung in manifest.json), Startkonfiguration maler-drews auf Port 8093. Acht Seiten (Start, Aktuelles, Unsere Projekte, Kunstmalerei, Kontakt, Impressum, Datenschutz, 404), 31 Beiträge mit Bildkopf, Text, Galerie mit Bildbetrachter, Nachbarn und Aufruf, Kategorien „Aktuelles“ (5) und „Projekte“ (26); die Listen sind Beitragslisten-Bausteine (Karten auf der Startseite, Liste als Zeitleiste, Wechselseitig als Zickzack). Kopf mit Marke und Versalmenü, Fuß in vier Spalten mit Instagram, Öffnungszeiten und Rechtszeile. Formular serverseitig, Google-Karte und elf Videos als eingebettete Inhalte hinter der Einwilligungsleiste. Impressum und Datenschutz wörtlich übernommen (die Datenschutzerklärung nennt Google Analytics und reCAPTCHA, die der Nachbau nicht nutzt – vor dem Livegang zu bereinigen). Weiterleitungen für die drei toten Vorlagenadressen und die eine über 80 Zeichen lange Beitragsadresse. Bewusste Abweichungen stehen im Kopf des Skripts.

Für 100 geändert, nicht für die Optik: Gold auf Weiß erreicht 2,4:1 – Kleinzeilen, Datum, Links und aktive Menüpunkte auf hellem Grund tragen @gold-text (#8a6a2f, 5,0:1), Buttons dunkle statt weiße Schrift auf Gold (6:1), der Einwilligungsknopf ebenso; vor den Beitragslisten steht eine nur für Vorlesesoftware sichtbare H2; der Copyright-Link ist unterstrichen. Das Startbild wird über den äußeren Abschnitt vorgeladen (nur das erste Hintergrundbild einer Seite bekommt ein preload).

Zwei Lücken im CMS behoben (site.css, page.html.twig, unveröffentlicht, gehört in 1.16.1): Abschnitte mit Breite „breit“ oder „voll“ blieben so schmal wie alle anderen, weil .kant .shell{max-width:var(--k-container)} des Designs die Breitenregeln schlug – jetzt mit .kant davor. Der Nach-oben-Knopf stand außerhalb jeder Landmarke (axe „region“) und steckt jetzt in einem nav. Beim Bauen gelernt: eigenes CSS erlaubt kein @media, keine Anführungszeichen, keine Klammern in Selektoren (:not(), nth-child()), keine Kommaselektoren, und eine leere &{…}-Regel geht verloren – Eigenschaften des Elements ohne &{} schreiben.

Geprüft: Lighthouse 13.4.1 (scripts/audit.mjs, je drei Läufe Mobil und Desktop, Mediane) gegen den lokalen Produktionsmodus (APP_ENV=prod, Port 8092) auf sechs Adressen (/, /aktuelles, /unsere-projekte, /kunstmalerei, /kontakt, /friedrichsbad-baden-baden): Barrierefreiheit, Best Practices, SEO und agentisches Browsing überall 100, Leistung 98–100 (Startseite 99 mobil / 98 Desktop, Kunstmalerei 99/99, Projekte und Beitrag 99 mobil, sonst 100; LCP ist jeweils das Foto hinter dem Seitenkopf, mobil 1,6–2,1 s, Desktop 0,6–1,0 s; CLS ≤ 0,01, TBT ≤ 7 ms). Berichte in reports/lighthouse-maler-drews/. Drei Messrunden davor zeigten Barrierefreiheit 91–93 (Gold-Kontrast, Überschriftenfolge, Link nur durch Farbe), dann 96–100 (aktiver Menüpunkt, Formularbeschriftungen), jetzt 100. axe (WCAG 2.0/2.1 A+AA und Best Practice, Playwright, 1440 und 390 px, nach dem Durchscrollen) auf neun Seiten einschließlich Impressum und Datenschutz: null Verstöße (var/drews-axe.mjs); vorher 58, davon 36 Kontrast, 18 „region“ am Nach-oben-Knopf, 2 Überschriftenfolge, 2 Link im Text. Maschinenlesbar: llms.txt mit allen Seiten und Beiträgen, sitemap.xml mit 41 Adressen, robots.txt, feed.xml, LocalBusiness mit Adresse, Öffnungszeiten, Einsatzgebiet und Instagram als sameAs. Alle 41 Adressen der Vorlage antworten mit 200 oder 301. Screenshots des Nachbaus in 1440 und 390 px gegen die der Vorlage verglichen (Startseite, Aktuelles, Projekte, Kunstmalerei, Kontakt, Beitrag, Impressum). PHP-Tests 512 grün (PHP 8.4), npm test 33 grün, Twig-Lint, php-cs-fixer auf den neuen Skripten.

Nicht geprüft: der Editor auf den Drews-Dokumenten im Browser (kein Anmelden mit Passwort in dieser Sitzung), Firefox/Safari, ein echtes Touchgerät, das Hosting. Die Videos liegen weiter auf maler-drews.com und brauchen für den Livegang einen eigenen Speicherort.

1.16.0 veröffentlicht (15.09.2026)

Tag v1.16.0 (Commit 32ee288), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.16.0 – Spaltenbreiten einer Zeile durch Ziehen im Editor.

Gebaut aus einem sauberen Checkout des Tags (git worktree add var/release-source-1.16.0 v1.16.0, php scripts/build-release.php darin): 227 Quelldateien, vendor/ ohne Entwicklungspakete, 3549 Dateien im Archiv.

Anhänge: kantsites-1.16.0.zip (4,7 MB, SHA-256 8aa8d40d79dfe7bf972ca005ed92d688cf63beb93a4807c20d5dcabb9b2f9264), .sha256, install.php mit eingetragener Prüfsumme; hochgeladen mit var/publish-release-1.16.0.py, erst Entwurf, dann veröffentlicht.

Geprüft: HTTP-Archiv-Update 1.15.1 → 1.16.0 ok (Update 302, System 200, Abschluss 302, öffentliche Seite 200; scripts/archive-update-check.mjs); nach der Veröffentlichung ohne Anmeldung alle drei Anhänge bytegleich, latest = v1.16.0. Die Prüfungen des Codes selbst stehen im Eintrag darunter.

Nächster Schritt: Update auf web.kant-it.de über System → Update aus dem Archiv (keine Migration), dann im Editor eine Zeile wählen und die Spaltengrenze ziehen – etwa bei einer Karte mit Symbol links und Text rechts.

1.16.0 – Spaltenbreiten einer Zeile durch Ziehen (15.09.2026)

Anlass: Umut, mit der Karte „HausKlar“ (Symbol links, Text rechts) als Beispiel: „bei Rows die Breite ändern, am besten per Drag and Drop im Editor.“ Zeilen kannten bisher nur gleich breite Spalten. Entscheidung und Begründung: ADR 098.

Umsetzung: Neue freie Stileigenschaft columnWidths an der Zeile (Typ fractions, StyleSchema::FRACTIONS, z. B. 66.7 33.3), je Gerät, ohne neues Dokumentschema. StyleCompiler::columnTracks() macht daraus grid-template-columns:minmax(0,66.7fr) minmax(0,33.3fr), füllt fehlende Anteile mit dem Durchschnitt auf, kappt überzählige und setzt ein Gerät, dessen Aufteilung stapelt (one oder gridColumns 1), auf eine Spur – sonst schlüge die ID-Regel des Desktops die Stapel-Klasse des Handys. Im Editor (canvas-overlay.js) liegt auf jeder Grenze zwischen zwei nebeneinander stehenden Spalten der gewählten Zeile (oder der Zeile um die gewählte Spalte) ein Griff .ov-resize: Ziehen stellt die Zeile in der Vorschau live um, Loslassen schreibt einen Verlaufsschritt (onResize in editor.js → setStyle), Pfeiltasten verschieben um fünf Punkte und behalten den Fokus. Ausgangswert sind die gemessenen Spaltenbreiten. Im Design-Bedienfeld gibt es dazu das Textfeld „Spaltenbreiten“ (Layout, nur bei Zeilen). Kein Griff bei gestapelten Spalten und während ein Element gezogen wird.

Geprüft: phpunit vollständig mit PHP 8.4 512 von 512 grün, mit PHP 8.2 512 grün mit der vorbestehenden Warnung in UpdaterTest; neu tests/ColumnWidthsTest.php (Spuren, Stapeln je Gerät, Auffüllen und Kappen, Ablehnung ungültiger Werte). npm test 33 grün (Relevanz nur für Zeilen). npm run test:editor mit PHP_BINARY=C:/php84/php.exe vollständig bestanden – neu darin: echter Mauszug am Griff verbreitert die erste Spalte, der Wert steht im Bedienfeld der Zeile, auf „Mobil“ gibt es keinen Griff, ein Rückgängig stellt gleiche Spalten her (reports/editor/column-widths.png). php-cs-fixer ohne Befund. Ein erster Lauf des Checks scheiterte an openGroup, das im Skript erst später definiert ist – die Gruppe wird jetzt an Ort und Stelle geöffnet.

Nebenbei: .claude/launch.json trägt in der Arbeitskopie den noch nicht committeten Eintrag maler-drews einer anderen Sitzung auf Port 8091; die Pilotkonfiguration ist deshalb lokal auf 8092 verschoben, die Datei bleibt aus diesem Commit heraus.

1.15.1 veröffentlicht (15.09.2026)

Tag v1.15.1 (Commit d05dba3), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.15.1 – eigene Absenderadresse je Formular.

Gebaut aus einem sauberen Checkout des Tags (git worktree add var/release-source-1.15.1 v1.15.1, php scripts/build-release.php darin): 227 Quelldateien, vendor/ ohne Entwicklungspakete, 3549 Dateien im Archiv.

Anhänge: kantsites-1.15.1.zip (4,7 MB, SHA-256 e15a7a69f299c16d86d372b65610b52f15b40b93904fa6402ea6991de7ed21f7), .sha256, install.php mit eingetragener Prüfsumme; hochgeladen mit var/publish-release-1.15.1.py, erst Entwurf, dann veröffentlicht.

Geprüft: HTTP-Archiv-Update 1.15.0 → 1.15.1 ok (Update 302, System 200, Abschluss 302, öffentliche Seite 200; scripts/archive-update-check.mjs); nach der Veröffentlichung ohne Anmeldung alle drei Anhänge bytegleich, latest = v1.15.1. Die Prüfungen des Codes selbst stehen im Eintrag darunter.

Nächster Schritt: Update auf web.kant-it.de über System → Update aus dem Archiv (keine Migration), dann unter Formulare eine Absenderadresse der eigenen Domain eintragen und eine Testanfrage senden.

1.15.1 – Eigene Absenderadresse je Formular (15.09.2026)

Anlass: Umut: „Versandadresse bei Kontaktformularen sollte man noch festlegen können – aktuell wird's über die gleiche Adresse verschickt wie der Empfänger?" Stimmte: FormController::deliver() setzte From auf die Empfängeradresse. Das geht schief, sobald der Empfänger ein Postfach bei einem anderen Anbieter ist (z. B. eine GMX- oder Gmail-Adresse), weil viele Hoster nur zustellen, was von einer Adresse der eigenen Domain kommt, und SPF/DMARC des fremden Anbieters die Post als Fälschung abweisen.

Umsetzung: Neue Formulareinstellung sender (freiwillig). FormRepository::validateSettings() prüft sie wie den Empfänger mit FILTER_VALIDATE_EMAIL, leer bleibt erlaubt; die Verwaltung (forms.html.twig, Abschnitt Zustellung, Feld „Absender“ direkt unter dem Empfänger mit Hinweis) und FormAdminController reichen sie durch. Beim Versand gilt sender ?: recipient – bestehende Formulare ohne den Schlüssel verhalten sich wie bisher, es ist keine Migration nötig. Die Antwortadresse aus dem Formularfeld bleibt Reply-To.

Geprüft: phpunit vollständig mit PHP 8.4 509 von 509 grün, mit PHP 8.2 509 grün mit der vorbestehenden Warnung in UpdaterTest; npm test 33 grün; npm run test:editor mit PHP_BINARY=C:/php84/php.exe vollständig bestanden, darin die axe-Prüfung der Formularseite. Neu in EditorSecurityTest: testAFormMaySendFromItsOwnAddress (From = Absender, To = Empfänger, Reply-To = Feld; ungültiger Absender abgewiesen, Leerzeichen werden zu leer) und im bestehenden Absendetest die Prüfung, dass ohne Absender From = To ist – beides über getMailerMessage() mit dem null://-Transport der Tests. php-cs-fixer ohne Befund. In der Pilotkopie im Browser (kantsites-pilot, jetzt Port 8091, weil 8087 von einer anderen Sitzung belegt war): Feld sichtbar, formular@kant-it.de gespeichert und nach dem Neuladen erhalten, Meldung „Das Formular wurde gespeichert.“ Ein echter Versand über SMTP wurde nicht geprüft.

Nächster Schritt: Auf web.kant-it.de nach dem nächsten Update unter Formulare eine Absenderadresse der eigenen Domain eintragen (z. B. formular@kant-it.de), dann eine Testanfrage senden und im Postfach Absender und Zustellung kontrollieren.

1.15.0 veröffentlicht (15.09.2026)

Tag v1.15.0 (Commit 160c394), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.15.0 – Formulare, Statistik, Design und Seitenleiste der Verwaltung überarbeitet, Formulare mit eigenen Farben und Vorschau.

Gebaut aus einem sauberen Checkout des Tags (git worktree add var/release-source-1.15.0 v1.15.0, php scripts/build-release.php darin): 227 Quelldateien, vendor/ ohne Entwicklungspakete, 3549 Dateien im Archiv.

Anhänge: kantsites-1.15.0.zip (4,7 MB, SHA-256 86ef728812db3b2f565f07dd1d8aebf8d5d41b75875695544134684b77a193ed), .sha256, install.php mit eingetragener Prüfsumme; hochgeladen mit var/publish-release-1.15.0.py, erst Entwurf, dann veröffentlicht.

Geprüft: HTTP-Archiv-Update 1.14.2 → 1.15.0 ok (Update 302, System 200, Abschluss 302, öffentliche Seite 200; scripts/archive-update-check.mjs, aus dem Hauptrepository heraus, weil der saubere Checkout weder vendor/ noch var/kantit.db hat); nach der Veröffentlichung ohne Anmeldung alle drei Anhänge bytegleich, latest = v1.15.0, Installeradresse 200. Die Prüfungen des Codes selbst stehen im Eintrag darunter.

Nächster Schritt: Update auf web.kant-it.de über System → Update aus dem Archiv (keine Migration), dann unter Formulare das Aussehen des Kontaktformulars wählen.

1.15.0 – Formulare, Statistik, Design und Seitenleiste überarbeitet (15.09.2026)

Anlass: Umut fand die Formularanlage unschön und wollte Felder löschen können, die Statistik „lieblos“, auf der Designseite vordefinierte Farben, eigene Farben für Link- und Seitenfarbe und eine Vorschau der Schriften, und den Fuß der Seitenleiste (Fassung, Benutzer, Abmelden) besser. Nachgeschoben: eine Vorschau des Kontaktformulars, in der Rahmen-, Hintergrund- und Buttonfarbe direkt gewählt werden.

Formulare (forms.html.twig, neu form-editor.js). Jedes Feld ist eine Karte mit Nummer, Beschriftung, Typ, technischem Name, Hilfetext, Pflicht-Schalter und einem Entfernen-Knopf; „Feld hinzufügen“ klont eine <template>-Karte. Der technische Name folgt der Beschriftung (Größe der Fläche → groesse_der_flaeche), bis jemand ihn selbst schreibt; Optionen erscheinen nur bei einer Auswahl; eine Einwilligung ist sichtbar immer Pflicht. Ohne Skript bleibt alles bedienbar: eine Karte ohne Beschriftung wird nicht gespeichert. Neu ist der Abschnitt Aussehen: Buttonbeschriftung, Rahmen und Hintergrund der Felder, Button- und Buttonschriftfarbe, jeweils aus der Palette oder als eigener Wert (FormRepository::COLORS, geprüft mit StyleSchema::COLOR). Daneben eine Vorschau, die jeder Eingabe folgt und den Absendeknopf mit dem Standardbutton der Website zeichnet (buttonSample). Auf der Website schreibt FormRepository::css() je Formular eine Regel .block-form[data-form="…"]{…} in den gehashten Stylesblock (PageController::renderPage); Felder lesen --form-border/--form-bg (site.css), der Button bekommt --kb-fill/--kb-ink und die Darstellungsvariablen neu – am Wurzelelement wären sie schon mit den alten Werten aufgelöst. Das „Neues Formular“ ist ein zugeklapptes Karten-Details am Ende.

Statistik (statistics.html.twig, Statistics::overview()). Sechs Kennzahlkacheln: Aufrufe mit Vergleich zum gleich langen Zeitraum davor (previousTotal), Schnitt je Tag, stärkster Tag mit Datum und Wochentag (busiestDay), aufgerufene Seiten (pageCount), Aufrufe über Verweise (referred) und nicht gefundene Adressen (missingTotal). Der Verlauf hat Rasterlinien, jeder Balken einen <title> mit Tag und Zahl, der stärkste Tag ist hervorgehoben; dazu ein neues Wochentagsdiagramm im Schnitt (weekdays). Die Ranglisten tragen Rang und einen Anteilsbalken (SVG-Breite, kein Inline-Stil). Der Zeitraum ist eine Leiste mit gedrücktem Knopf in der Kopfzeile; „Was diese Zahlen nicht sagen“ ist zugeklappt.

Design (theme.html.twig, neu theme-panel.js). Die Palette sind Farbkarten, deren Fläche der Farbwähler selbst ist, mit Hexwert, Name, Schlüssel und Entfernen. Sieben fertige Paletten (Theme::PALETTES: Wald, Ozean, Sand, Koralle, Schiefer, Olive, Nacht; die Linkfarbe erreicht auf dem Papierton mindestens 4,5:1) ersetzen die fünf Grundschlüssel und lassen eigene Farben stehen. Schriften sind Karten mit einer Probe in der Schrift selbst (lokale fonts.css wird geladen, wenn vorhanden – FontLibrary::hasStylesheet()), Schnitte als Chips, die Auswahlen für Fließtext/Überschriften/Button folgen neuen Schriften. Text-, Seiten- und Linkfarbe nutzen das Palettenbedienelement des Editors (createColorControl), das bei jeder Palettenänderung neu aufgebaut wird; daneben eine Typografie-Vorschau mit Überschriftenskala, Zeilenhöhe, Farben und Unterstreichung.

Seitenleiste (shell.html.twig). Unten eine Kontokarte: Avatar, Name, Rolle (Administrator/Veröffentlicher/Redakteur), rechts ein Abmeldesymbol mit Beschriftung für Vorlesesoftware; darunter „KantSites 1.14.2“ mit dem Update-Hinweis in einer Zeile. Neue Symbole logout, plus, remove. Auf Tablets wischt nur noch die Liste, Konto und Abmelden bleiben rechts im Bild; dafür braucht die Rasterspalte minmax(0, 1fr), sonst schob die Leiste die Seite über den Rand.

Die Formularvorschau nennt den Kontrast der Buttonschrift auf der Buttonfarbe (WCAG-Formel, grün ab 4,5:1, gelb ab 3:1, sonst rot), damit eine unlesbare Wahl auffällt, bevor sie auf der Website steht. Die pattern-Attribute der Schlüsselfelder maskieren den Bindestrich ([a-z][a-z0-9\-]*): Chrome prüft pattern inzwischen mit dem v-Flag und lehnte das alte Muster mit einem Konsolenfehler ab – die Prüfung war damit wirkungslos.

Geprüft: phpunit mit PHP 8.4 508 von 508 grün, mit PHP 8.2 508 grün mit einer vorbestehenden Warnung in UpdaterTest (Git-Umgebung); neu getestet: Formularfarben auf der Seite samt CSP-Hash, Ablehnung ungültiger Farben, Statistiksummen und Vorzeitraum, drei Diagramme mit Titeln. npm run test:editor vollständig bestanden – darin die axe-Prüfung von Formularen, Statistik und Design. test:buttons besteht den Designseiten-Teil (Vorlage wählen, speichern, Werte erhalten) und scheitert danach wie test:navigation-ux an einem Klick auf den Strukturbaum des Editors (#structure [data-block-id=…] „not visible“) – eine Stelle außerhalb dieser Änderung, nicht weiter untersucht. test:admin scheitert vor den Formularen an zwei vorbestehenden Befunden: dem Wiedergabeknopf des öffentlichen Sliders bei reduzierter Bewegung und nested-interactive in den Menü-Zusammenfassungen (Ziehgriff im summary). In der Pilotkopie im Browser: Feld hinzufügen/entfernen, Namensableitung, Farbwahl mit Vorschau und Kontrasthinweis, Speichern, Regel und Buttonfarbe auf der öffentlichen Startseite; Palette „Ozean“ übernehmen, Schrift hinzufügen, Linkfarbe wählen; Seitenleiste und Bildschirme auf 1440, 900 und 375 px. Codestil mit php-cs-fixer (verbleibende Abweichung in tests/CmsTest.php ist vorbestehend). Veröffentlicht als 1.15.0, siehe Eintrag oben.

1.14.2 veröffentlicht (15.09.2026)

Tag v1.14.2 (Commit 0aa1ff6), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.14.2 – Fußzeile des Einwilligungshinweises: Einzelwahl links, Rechtslinks rechts.

Gebaut aus einem sauberen Checkout des Tags (git worktree add var/release-source-1.14.2 v1.14.2), nicht aus der Arbeitskopie: dort lagen zu diesem Zeitpunkt nicht committete Änderungen einer anderen Sitzung (site.css, archive.html.twig, page.html.twig, tests/CmsTest.php, neu src/Twig/HeaderExtension.php), und ein erster Build aus der Arbeitskopie hatte sie mitgenommen – der wurde verworfen. Das Archiv enthält keine vorgefertigten assets/build/-Bündel mehr (die Arbeitskopie hatte welche, sie entstehen beim ersten Aufruf); daher 3546 statt 3552 Dateien.

Anhänge: kantsites-1.14.2.zip (4,7 MB, SHA-256 236dff086267591a51198b55ddfbaf2ee999db8a081ec5bb1d28752260a3db3f), .sha256, install.php; hochgeladen mit var/publish-release-1.14.2.py, erst Entwurf, dann veröffentlicht.

Geprüft: PHP 506 Tests grün (ein erster Lauf zeigte einen Fehler, während die andere Sitzung tests/CmsTest.php bearbeitete; zwei weitere Läufe grün); HTTP-Archiv-Update 1.14.1 → 1.14.2 ok; nach der Veröffentlichung ohne Anmeldung alle drei Anhänge bytegleich, latest = v1.14.2, Installeradresse 200.

1.14.2 – Einwilligungshinweis: Fußzeile aufgeräumt (15.09.2026)

Anlass: Umut fand „Einzeln wählen“ und die Rechtslinks unordentlich und schlug links/rechts vor.

Umgesetzt (consent-banner.html.twig, site.css): Beide stehen in einer Zeile .consent-foot – Einzelwahl links, Datenschutz · Impressum rechts, auf gemeinsamer Grundlinie. Aufgeklappt nimmt die Einzelwahl die volle Breite ([open]{flex-basis:100%}), die Links rutschen rechtsbündig darunter. Die Links stehen ohne Punkt dazwischen mit 14 px Abstand; ohne beide Adressen entfällt die Zeile. Ein Stolperstein: .consent-bar p schlug .consent-links und nahm den rechten Rand weg – der Selektor heißt jetzt .consent-bar p.consent-links.

Geprüft: vier Gestaltungsvarianten auf 1440 und 390 px, zu- und aufgeklappt (Screenshots reports/consent-look/*-offen.png), sechs Einwilligungstests grün. Veröffentlicht als 1.14.2, siehe Eintrag oben.

1.14.1 veröffentlicht (15.09.2026)

Tag v1.14.1 (Commit 0bdcc7f), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.14.1. Enthält den gestaltbaren Einwilligungshinweis, den bei offener Frage erreichbaren Nach-oben-Knopf, den korrigierten Abstand unten und die orangen Beschriftungen im Editor; dazu 860bed9 (Seed-Skript Tagespflege Moldovan, nicht im Archiv).

Anhänge: kantsites-1.14.1.zip (4,7 MB, 3552 Dateien, SHA-256 c0620a3155db894373199dbe2e286744dd7b21eed2c355e8d2a890652d1d203e), .sha256, install.php mit eingetragener Prüfsumme; hochgeladen mit var/publish-release-1.14.1.py, erst Entwurf, dann veröffentlicht.

Geprüft: PHP 505 Tests grün; browser-check.mjs, editor-check.mjs bestanden; HTTP-Archiv-Update 1.14.0 → 1.14.1 ok (Update 302, System 200, Abschluss 302, öffentliche Seite 200). Nach der Veröffentlichung ohne Anmeldung: alle drei Anhänge bytegleich, /releases/latest nennt v1.14.1, Installeradresse antwortet 200.

Nächster Schritt: Update auf web.kant-it.de über System → Update aus dem Archiv, Migrationen abschließen (keine neuen), dann unter Einstellungen → Einwilligung das Aussehen wählen; var/kantit.db mit dem korrigierten Kopfmenü hochladen.

1.14.1 – Einwilligungshinweis gestaltbar, Nach-oben-Knopf bei offener Frage, orange Beschriftungen (15.09.2026)

Anlass: Umut wollte das Banner gestaltbar (rund, Farben) und schöner, den Abstand unten korrigiert, belegte Eigenschaften im Editor orange statt nur fett mit Punkt, und Gerätechips, die sich „aktiv überlagern“.

Einwilligungshinweis (ADR 097). Sieben neue Einstellungen: consentLayout (karte/leiste/ecke), consentShape (weich/rund/eckig), consentTitle, consentColor, consentInk, consentAccent, consentAccentInk; Farben mit StyleSchema::COLOR geprüft, Palette über Theme::cssColor() (neu, ersetzt die Kopie im Nach-oben-Knopf). Consent::styles() schreibt :root{--kant-consent-…} in den gehashten Stylesblock; die Anordnung ist eine Klasse. Neues Grundlayout: Streifen und Symbol in der Knopffarbe, gleich breite Knöpfe, aufklappbares „Einzeln wählen“ mit Pfeil, unterstrichene Rechtslinks; Leiste auf breiten Bildschirmen zweispaltig. Archiv und /einwilligung bekommen Theme- und Hinweisvariablen als gehashten Stylesblock – das Archiv trug bisher gar keine Palette.

Abstand unten: 320 px bis 620 px (Leiste bis 299 px hoch), 270 px bis 899 px, 200 px darüber; die Fläche scrollt ab 100dvh − 24px in sich. Nach-oben-Knopf hebt sich bei Leiste und Ecke auf jeder Breite (:has()), bei der Karte bis 799 px.

Editor: belegte Beschriftungen in --used (#9d4e0d, 5,6:1); Chips mit 6 px Lücke, Fokusring ohne Versatz, Gruppe flex:none. Ein Überlagern der Chips ließ sich in 1.14.0 nicht reproduzieren – am Handy (390 px) liegen die drei Chips bei 22 px Breite mit 6 px Abstand, kein outline gezeichnet; die Rückmeldung bezog sich wohl auf den outline-Ring von 1.13.0.

Geprüft: PHP 505 Tests grün, darunter neu testTheConsentQuestionTakesLayoutShapeAndColoursFromTheSettings (Standard, eigene Werte, unbekannter Palettenschlüssel fällt weg, Einwilligungsseite mit Hash, ungültige Farbe abgewiesen). Chromium: vier Varianten (Karte, Leiste, Ecke rund dunkel, Karte eckig dunkel) auf 1440 und 390 px – Klasse, Radien, Farben und Überschrift stimmen, Nach-oben-Knopf sichtbar, treffbar, ohne Überlappung; /einwilligung 200 mit gehashtem style-src. Nach-oben-Prüfung auf acht Breiten bestanden. browser-check.mjs, editor-check.mjs (Einstellungen, Einwilligung) und axe über die Editorseitenleiste bestanden. Screenshots: reports/consent-look/, reports/admin-hints/.

Nicht geprüft: echtes iOS-Safari; der @supports not-Zweig; Lighthouse.

Nach-oben-Knopf bei offener Einwilligungsfrage (15.09.2026)

Anlass: Umut meldete, der Nach-oben-Knopf funktioniere auf dem Handy nicht.

Befund. Der Knopf selbst funktioniert auf dem Telefon: In iPhone- und Pixel-Emulation (Chromium), lokal wie auf web.kant-it.de (dort bereits 1.14.0 mit scrolled.js), erscheint er nach 500 px, ist treffbar, und Tippen springt nach oben – sobald die Einwilligungsfrage beantwortet ist. Vorher galt body.consent-open .scroll-top{display:none} auf allen Geräten. Live trägt jeder Erstbesuch consent-open, und auf dem Telefon bleibt die Leiste unten oft einfach stehen – der Knopf kam dort nie. Kein Fehler im Skript und nicht der alte Safari-Fehler aus 1.13.0.

Korrektur (site.css, page.html.twig). Der Knopf bleibt bei offener Frage sichtbar. Gemessen deckt die Leiste die rechte Ecke nur bis knapp 800 px ab (bis 620 px volle Breite, 262–285 px hoch; darüber zentriert mit höchstens 620 px). In dieser Stufe setzt sich der Knopf per Ankerpositionierung 12 px auf die Oberkante der Leiste (anchor-name:--kant-consent, bottom:calc(anchor(top) + 12px)), unabhängig davon, wie hoch ihr Text umbricht. Browser ohne Ankerpositionierung blenden ihn dort weiter aus (@supports not). Ab 800 px bleibt er an seinem Platz. Weil ein Anker im Dokument vor dem positionierten Element stehen muss, gibt die Vorlage die Leiste jetzt vor dem Knopf aus.

Geprüft: Chromium, isolierte Kopie der Pilotdatenbank, Einwilligung offen auf 360, 390, 700, 768, 799, 800 und 1440 px sowie beantwortet auf 390 px – in allen acht Fällen sichtbar, keine Überlappung mit der Leiste, treffbar, nach dem Tippen scrollY 0. Twig-Lint gültig. PHP-Tests 504 grün, browser-check.mjs und editor-check.mjs (einschließlich Einwilligungsabläufe) bestanden.

Nicht geprüft: echtes iOS-Safari – WebKit ist für Playwright auf diesem Rechner nicht installiert. Der @supports not-Zweig wurde nicht ausgeführt, weil Chromium Ankerpositionierung kann.

Nebenbefund, nicht geändert: Bis 620 px reserviert body.consent-open 260 px Abstand unten, die Leiste ist mit Standardtext bis 285 px hoch – auf kleinen Telefonen liegen die letzten rund 25 px der Seite unter der offenen Leiste.

Nächster Schritt: nicht committet und nicht veröffentlicht; live erst mit einem Release (z. B. 1.14.1).

1.14.0 veröffentlicht (15.09.2026)

Tag v1.14.0 (Commit a434e0c), Release: https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.14.0. Enthält die Schriften vom eigenen Server, den Hinweisstreifen, System und Update auf dem Telefon, die Gerätechips sowie Öffnungszeiten, Einsatzgebiet und FAQ in den strukturierten Daten.

Anhänge: kantsites-1.14.0.zip (4,7 MB, SHA-256 8360566b1eaed670b29f506262953d3b17f95893e23c7f4a940ff6d5e3f62735), .sha256, install.php mit eingetragener Prüfsumme. Hochgeladen mit var/publish-release-1.14.0.py über den Git-Zugang dieses Rechners, erst als Entwurf, dann veröffentlicht.

Geprüft: 504 PHP-Tests grün; browser-check.mjs und editor-check.mjs bestanden; HTTP-Archiv-Update 1.12.1 → 1.14.0 ok. Nach der Veröffentlichung ohne Anmeldung: alle drei Anhänge bytegleich mit den lokalen Dateien, /releases/latest nennt v1.14.0, die Installeradresse …/releases/download/v1.14.0/kantsites-1.14.0.zip antwortet mit 200.

Nächster Schritt: Update auf web.kant-it.de über System → Update aus dem Archiv, danach Migrationen abschließen und var/kantit.db mit dem korrigierten Kopfmenü hochladen.

Globale Hinweise, der Schriftenknopf und die Gerätechips (15.09.2026)

Anlass: Umut fehlte ein Hinweis, der auch außerhalb der Systemseite sagt, dass etwas zu tun ist; der Knopf „Schriften holen“ sah nicht aus wie die übrigen; und im Designbereich störte ein roter Rahmen an den Gerätechips, der abgeschnitten wirkte.

Hinweisstreifen auf jedem Verwaltungsbildschirm (kant_admin_notices(), admin/shell.html.twig). Über dem Inhalt steht jetzt, was einen Knopf braucht, und der Knopf gleich daneben: „Fassung X ist verfügbar“ → Jetzt aktualisieren (System), „Schriftdateien fehlen: …“ → Schriften holen (Design). Keine Zeile davon geht ins Netz – die verfügbare Fassung steht im Protokoll der täglichen Aufgabe update-check, die fehlenden Schriften sind ein Blick in den Ordner. Der Updatehinweis erscheint nur für ROLE_ADMIN, weil nur die auf die Systemseite dürfen. Warm statt rot: es ist eine Aufgabe, kein Fehler. Auf 390 px bricht der Streifen um, der Knopf rutscht unter den Text.

Schriftenknopf. Er trug gar keine Klasse und war deshalb der einzige Knopf der Verwaltung im Standardaussehen des Browsers. Jetzt primary (bzw. secondary beim erneuten Holen), und der ganze Abschnitt steht wie alles andere in einer Karte mit Kopf und Körper.

Gerätechips D/T/M (editor.css). Der rote Kasten war outline:2px mit outline-offset:1px auf dem gerade bearbeiteten Gerät. Ein outline liegt außerhalb des Kästchens: bei 2 px Abstand lief er unter den Nachbarchip und wurde von der seitlich beschnittenen Sidebar gekappt – daher der abgeschnittene Eindruck. Jetzt zwei Zustände mit zwei Mitteln:

  • belegt (dieses Gerät trägt einen eigenen Wert): gefülltes Orange --used: #9d4e0d, weiße Schrift – 5,6:1 Kontrast. Dieselbe Farbe trägt der Punkt vor der Beschriftung, damit „hier steht ein eigener Wert“ überall gleich aussieht.
  • gerade bearbeitet: 2 px Rahmen innen (border-width, kein outline), auf belegten Chips zusätzlich ein weißer Innenring. Nichts ragt mehr aus dem Kästchen, nichts wird beschnitten.

Chips von 19 auf 22 px, Abstand von 2 auf 4 px – auf einen Blick lesbar, welche Geräte an einer Eigenschaft hängen.

Geprüft (isolierte Kopie der Pilotdatenbank, künstlich auf „Lato 300 fehlt“ und „Fassung 9.9.9 verfügbar“ gesetzt): beide Streifen erscheinen auf /admin, tragen die richtigen Ziele, und auf 390 px bleibt die Dokumentbreite bei 390. Schriftenknopf trägt primary. Chips: belegte auf rgb(157,78,13), bearbeitete mit 2 px Rahmen, kein gezeichnetes outline mehr. axe über die Editorseitenleiste ohne Verstöße. PHP-Tests: 498, ein bekannter Fehler (testAnyPublishedPageCanBecomeTheRootAndEverythingFollows). Screenshots: reports/admin-hints/.

Zu beachten: admin.css und editor.css hängen an ?v={{ kant_version() }}. Bis die VERSION steigt, liefert der Browser die alten Fassungen aus dem Zwischenspeicher.

Schriften nur noch vom eigenen Server, kein sichtbarer Schriftwechsel mehr (14.09.2026)

Anlass: Umut sah im Browser, wie die Schrift kurz von fett auf dünn sprang, und stellte zwei Regeln auf: keine Schriften über Adressen von Google, und kein Nachladen nach dem fertigen Seitenaufbau.

Zwei getrennte Ursachen, beide belegt.

  1. Der späte Schnitt. FontLibrary::preload() holte je Familie nur die ersten zwei Schnitte vorab. Das Kant-IT-Theme nennt Lato 400, 700 und 900; die Überschriften stehen auf 800/900 und landen damit auf Lato 900 – dem einzigen Schnitt ohne Vorabholen. Gemessen am Nachbau: lato-400 und lato-700 starteten bei 151 ms, lato-900 erst bei 156 ms, also nach dem Stylesheet. Mit font-display:swap zeichnet der Browser die Überschrift bis dahin in der Ersatzschrift und tauscht sie danach – der gemeldete Sprung. Jetzt wird jeder Schnitt des Themes vorab geholt (lateinischer Grundbereich; die erweiterten Bereiche bleiben draußen).
  2. Google in der Editorvorschau. EditorController und PartialController gaben der Vorlage googleFonts und kein localFonts. In der Vorschau kamen die Schriften damit bei jedem Aufruf von fonts.googleapis.com und fonts.gstatic.com – zwei fremde Verbindungen hintereinander, und genau dort arbeitet Umut am meisten. Beide Vorschauen nutzen jetzt dieselben lokalen Dateien wie die öffentliche Seite.

Die Regel steht jetzt im Code, nicht in der Einstellung. page.html.twig hat keinen Google-Zweig mehr; fehlen die Schriftdateien, zeigt die Website die Ersatzschrift des Themes. Die Kopfzeilen lassen keinen fremden Schrifthost mehr zu – öffentlich font-src 'self', in der Verwaltung style-src 'self'; font-src 'self' statt der bisherigen Ausnahmen für fonts.googleapis.com/fonts.gstatic.com. Theme::googleFontsUrl() ist entfernt; das Herunterladen der Dateien über die Theme-Seite bleibt unverändert und baut seine Adresse selbst. Die Vorabholen-Verweise stehen jetzt vor dem Stylesheet, und die Texte auf der Theme-Seite sagen nicht mehr „kommen zurzeit von Google“, sondern „die Dateien fehlen, bis dahin die Ersatzschrift“.

Geprüft (Chromium, kalter Zwischenspeicher, isolierte Kopie der Pilotdatenbank): alle drei Schriftdateien starten bei 96 ms und sind bei 166–168 ms fertig, der erste sichtbare Inhalt erscheint bei 292 ms – die Schriften stehen also vor dem ersten Zeichnen, es gibt nichts mehr zu tauschen. Jede Datei wird genau einmal angefragt. Über die ganze Sitzung – öffentliche Seite, Anmeldung, Seiteneditor mit Vorschau, Bereiche – keine einzige Anfrage außerhalb des eigenen Servers. PHP-Tests: 498, ein bekannter Fehler (siehe unten). npm run test:browser und der Lighthouse-Lauf sind nicht gelaufen.

Tests angepasst: testFontsAreReadFromGoogleStylesheetAndServedFromHere prüft jetzt die Ersatzschrift statt des Google-Verweises und alle drei Vorabhol-Dateien; testThemeDrivesEveryPageAndGoogleFontsOpenTheirOwnPolicy heißt …AndNoFontIsEverFetchedFromGoogle und prüft die engere Kopfzeile. In testEveryAllowedIconHasADrawingAndTakesItsColourFromTheBlock stand eine echte Zeilenschaltung im Suchmuster; unter Windows mit core.autocrlf=true schlägt das fehl, sobald die Arbeitskopie CRLF trägt. Das Muster nimmt jetzt \R und gilt für beide Schreibweisen; die Vorlage selbst bleibt unverändert.

Weiterhin offen, nicht von dieser Aufgabe: CmsTest::testAnyPublishedPageCanBecomeTheRootAndEverythingFollows schlägt fehl – der Test erwartet <a href="/" aria-current="page">Start</a>, das Menü schreibt seit den Symbolen <span class="nav-label"> um die Beschriftung.

Kopfmenü im Hochformat und die Update-Ansicht auf dem Handy (14.09.2026)

Anlass: Umut meldete, das Mobilmenü im Kopf erscheine nur im Querformat, und bat darum, Texte und Ansichten der Update-Seite fürs Handy zu kürzen.

Befund zum Kopfmenü – falsch gebaut, kein Fehler im CMS. Der Kopfbereich trug zwei Menüblöcke: area-header-menu (waagerecht, mobile: display:none) und daneben area-header-burger (eigener Burger-Block, nur mobil sichtbar). Der veröffentlichte Stand auf web.kant-it.de kennt den zweiten Block nicht – abgerufen am 14.09.2026 enthält das Markup dort nur area-header-menu samt @media (max-width:767px){#area-header-menu{display:none}}. Damit ist im Hochformat (bis 767 px) gar kein Menü da, während im Querformat (768–1024 px) das Standardverhalten collapse: tablet denselben Block zum Burger zusammenlegt und ein Knopf erscheint. Genau die gemeldete Beobachtung. In var/kantit.db lag zwar ein zweiter Burger-Block, der das Loch stopfte, aber mit eigener Gestaltung: im Hochformat ein 161 px breiter Knopf, im Querformat der 101 px breite des waagerechten Menüs.

Behoben in var/kantit.db: ein einziger Menüblock area-header-menu mit collapse: tablet und burgerLabel: both, ohne display:none auf Mobil, burgerFontSize: 14px; der doppelte Block area-header-burger ist entfernt (Kopfbereich Fassung 8, Sicherung var/kantit.db.vor-kopfmenue-20260914-2244.bak). Geprüft am laufenden Nachbau: 375 px Hochformat und 844 px Querformat zeigen denselben Knopf („MENÜ“, 101 × 45 px, 14 px), die Liste bleibt in beiden verborgen und die aufklappende Fläche liegt über die volle Breite unter dem Kopf; ab 1025 px stehen die vier Einträge wieder waagerecht. Startkonfiguration kantit (Port 8130) in .claude/launch.json ergänzt.

Update-Ansicht (templates/admin/system.html.twig, public/assets/admin.css). Die Seite schob sich auf dem Handy seitlich aus dem Bild: .stack ist ein Raster mit automatischer Spalte, und eine automatische Rasterspalte wächst auf die Mindestbreite ihres breitesten Inhalts – eine lange Datenbankpfadangabe reichte. .stack bekommt minmax(0, 1fr), .facts beide Spalten als minmax(0, …) und unter 620 px nur noch eine, .card-body und die Prüfpunkte min-width: 0 samt overflow-wrap: anywhere. Die Sicherungstabelle wird unter 620 px zu gestapelten Blöcken (.table-stack, Spaltenüberschrift als data-label), statt waagerecht zu scrollen. Texte gekürzt und das Kleingedruckte in zugeklappte <details class="hint"> gelegt, ebenso die älteren 15 der 20 Protokollzeilen.

Geprüft: 390 × 844 mit Touch-Emulation gegen eine isolierte Kopie der Pilotdatenbank (tests/pilot-fixture.php, eigenes Zufallskonto): Dokumentbreite 390 statt 492, kein Element mehr breiter als der Inhaltsbereich, Wortzahl der Seite 1017 → 556, axe ohne Verstöße. /admin, /admin/media, /admin/menus, /admin/forms, /admin/theme, /admin/settings bleiben mobil bei 390 px; dieselben Seiten und /admin/system bei 1440 px unverändert breit. Screenshots: reports/system-mobile/.

Bekannter Fehler, nicht von dieser Aufgabe: CmsTest::testAnyPublishedPageCanBecomeTheRootAndEverythingFollows schlägt fehl (498 Tests, 1 Fehler) – der Test erwartet <a href="/" aria-current="page">Start</a>, das Menü schreibt seit den Symbolen <span class="nav-label"> um die Beschriftung. Auch ohne diese Änderungen und mit beiden PHP-Fassungen reproduzierbar.

Nächster Schritt: Umut spielt var/kantit.db auf den Server; erst dort ist das Kopfmenü im Hochformat live. Für die Admin-Änderungen die VERSION beim nächsten Release erhöhen, sonst liefert der Browser admin.css?v=1.13.0 weiter aus dem Zwischenspeicher.

1.14.0 – Öffnungszeiten, Einsatzgebiet und FAQ in den strukturierten Daten; Elektro Bernhard aktualisiert (14.09.2026)

Anlass: Umut nannte die tatsächlichen Bürozeiten (Mo–Fr 7:30–17:00), das Einsatzgebiet von Karlsruhe bis Offenburg und wollte die FAQ „mit allem, was notwendig ist“ – das JSON-LD, das das CMS bisher nicht hergab (siehe Lücke in den Einträgen vom Abgleich).

Umgesetzt (ADR 095): Einstellungen openingHours und areaServed mit Formular unter Unternehmensdaten; SiteSettings::organisation() gibt openingHoursSpecification (gleiche Zeiten zusammengefasst) und areaServed aus. Akkordeon-Eigenschaft faq (Schema v23, Kästchen im Editor); PageController baut daraus eine FAQPage mit Frage und Antwort aus dem sichtbaren Inhalt. Fünf neue Tests: Zusammenfassung und Reihenfolge der Zeiten, Abweisung unvollständiger oder verdrehter Zeiten, FAQPage nur aus gekennzeichneten Akkordeons und nur mit Antwort, Abweisung der Kennzeichnung vor v23, und – nach einem Fund der vollen Suite – dass ein Bildschirm, der die geladenen Einstellungen unverändert zurückgibt (Anlegen der Rechtsseiten, Installer), Öffnungszeiten und Einsatzgebiet nicht löscht.

Elektro Bernhard: Bürozeiten überall Mo–Fr 07:30–17:00 Uhr (Fuß, Kontakt, Beschreibung, FAQ); Einsatzgebiet Baden-Baden, Sinzheim, Hügelsheim, Iffezheim, Bühl, Rastatt, Kuppenheim, Muggensturm, Gaggenau, Gernsbach, Murgtal, Ettlingen, Karlsruhe, Achern, Oberkirch, Offenburg in Text und areaServed; FAQ um „Wann ist das Büro erreichbar?“ und „Wie läuft eine Anfrage ab?“ ergänzt und als FAQ gekennzeichnet. Die Öffnungszeiten weichen bewusst von der laufenden Website ab (dort Mo–Do 7:00–16:30, Fr bis 13:00) – Umuts Angabe gilt.

Arbeitsweise: Weil eine parallele Sitzung im Hauptarbeitsbaum dreizehn Dateien offen hatte, darunter Validator, PageController und Editor, entstand diese Änderung im eigenen Worktree var/wt-faq auf dem Zweig oeffnungszeiten-faq und wurde von dort auf main gebracht. Der Hauptarbeitsbaum muss danach git pull ausführen.

1.13.0 – Drei Symbole mehr, gestaltbarer Menü-Knopf, Nachbau „Tagespflege Moldovan“ (14.09.2026)

Anlass: Umut ließ die ZIP-Vorlage „Tagespflege Moldovan“ nachbauen. Beim Nachbau fehlten dem Symbolsatz genau die drei Bilder, die die Vorlage zeigt; danach kamen zwei Meldungen vom Telefon: das Menü sei nicht zu sehen, und „Scroll to top“ funktioniere nicht.

Nachbau (scripts/seed-tagespflege-moldovan.php, scripts/reset-tagespflege-moldovan.sh). Der Onepager aus dem Paket vom 09.09.2026, gebaut aus gewöhnlichen Bausteinen: Kopfleiste mit Telefonzeile, klebender weißer Kopf mit Wortmarke, Hero mit der limettenen Schnittfläche hinter dem Foto (clip-path als eigenes CSS auf einer Fläche), Notizkarte und Bildunterschrift, Werteband, Willkommen mit Porträt-Signatur, drei Angebotskarten, Zeitleiste mit Verbindungslinie aus Flächen, dunkler Kontaktbereich mit schräger Fläche, Formularkarte und Fuß mit limettenem Abschluss. Farben, Schriftgrößen, Abstände und Umbrüche sind aus style.css übernommen, ebenso die drei Gerätestufen. Eigene Installation: var/tagespflege-moldovan.db, var/tagespflege-moldovan-media, Bildquellen in var/tagespflege-moldovan-assets, Startkonfiguration auf Port 8089. Zwei bewusste Abweichungen: das Formular verschickt serverseitig statt eine Mailto-Nachricht vorzubereiten, und das Markenzeichen liegt als PNG vor, weil die Medienverwaltung JPEG, PNG und WebP nimmt. Impressum und Datenschutz fehlen wie in der Vorlage – für einen Livegang nachzutragen.

Symbole (ADR 094). people, music und home ergänzen den Satz in DocumentValidator::ICONS, blocks/icon.html.twig, editor.js und menu-editor.js. Ein Test hält Liste und Zeichnungen zusammen. .block-icon .block-icon{color:inherit} lässt die am Block eingestellte Farbe endlich an der Zeichnung ankommen – das eingebettete SVG trug dieselbe Klasse wie seine Hülle und setzte die Akzentfarbe erneut.

Menü-Knopf (ADR 094). Neue Eigenschaft burgerLabel (both, text, icon; „Nur Symbol“ behält den Namen als visually-hidden) und die neue Stilgruppe menu mit dem Ziel burger: burgerColor, burgerBackgroundColor, burgerBorderColor, burgerBorderWidth, burgerBorderRadius, burgerFontSize. Der Compiler schreibt #id .menu-burger>summary{…}. Im Kopfbereich klappt das Menü als Fläche über die volle Breite unter den Kopf auf statt inline in der schmalen Spalte neben der Marke – das war die gemeldete „unsichtbare“ Navigation.

Nach-oben-Knopf (ADR 094). Die Einblendung hängt nicht mehr an animation-timeline: scroll(), sondern an den Klassen kant-top-armed und kant-top-shown, die scrolled.js setzt; das Skript wird jetzt auch geladen, wenn nur dieser Knopf es braucht. Ohne Skript bleibt der Knopf sichtbar.

Geprüft: PHP-Suite 498 Tests (fünf neue: Symbolliste gegen Zeichnungen, drei Knopfvarianten, abgewiesene Variante, Knopffarben am summary); einziger Fehlschlag ist der bekannte Altfehler testAnyPublishedPageCanBecomeTheRoot…. 33 JavaScript-Tests, Twig-Lint, PHP-Stilprüfung sauber, Editor-Browserprüfung vollständig erfolgreich einschließlich axe. Playwright auf dem Nachbau bei 1440, 900 und 390 px sowie als iPhone 13: Seite gegen die Vorlage verglichen, Menü öffnet als Fläche unter dem Kopf, Sprungziele, 404-Seite, keine Konsolenfehler. Nach-oben-Knopf auf der Elektro-Bernhard-Installation als iPhone geprüft: oben verborgen, ab der Schwelle sichtbar, springt an den Anfang.

Veröffentlicht: Commit 73719ba und Tag v1.13.0 auf origin; Release 1.13.0 mit ZIP (3546 Dateien, 4,6 MB), SHA-256-Datei und install.php. Das Archiv wurde aus einem sauberen Worktree des Tags gebaut (var/release-source-1.13.0), vendor/ ohne Entwicklungspakete; die einzige .env im Archiv ist die Vorlage ohne Geheimnis. Die drei Anhänge wurden anonym von der Releaseadresse geladen und bytegenau mit den gebauten Dateien verglichen; SHA-256 14e333e0…59dcbf, identisch mit der Prüfsumme, die install.php mitbringt.

1.12.1 – Elektro Bernhard gemessen: Lighthouse und axe (14.09.2026)

Anlass: Umut fragte nach Google-Bewertung, SEO, llms.txt und Barrierefreiheit der neuen Website.

Ergebnis: Lighthouse-Mediane auf vier Seiten, Mobil und Desktop: alles 100, nur die mobile Startseite hat Leistung 99 (LCP 2,1 s durch das Hero-Foto). axe auf sechs Seiten in zwei Breiten: null Verstöße. Details und Messbedingungen in docs/QUALITY.md.

Behoben: drei Kontrast-/Gliederungsbefunde in der Website (Skript) und einer im CMS: der Pause-Knopf des Sliders verletzte WCAG 2.5.3 (sichtbarer Text „Pause“, Name „Automatische Wiedergabe pausieren“); slider.js bildet den Namen jetzt aus dem sichtbaren Text, scripts/admin-quality-check.mjs folgt.

Veröffentlicht: Commit 042aef9 und Tag v1.12.1 auf origin; Release 1.12.1 mit ZIP (3546 Dateien, 4,6 MB), SHA-256-Datei und install.php, gebaut aus dem sauberen Worktree var/release-source-1.12.1; alle drei Anhänge anonym geladen und bytegenau verglichen, SHA-256 c7a2de81…8f1a23.

1.12.0 – Soziale Profile als Baustein; Elektro Bernhard neu gestaltet (14.09.2026)

Anlass: Umut fand den ersten Nachbau „grauenhaft“, fragte nach dem transparenten Menü und vermisste Social-Icons im CMS – „gescheit“, also einmal zentral gepflegt und überall mit eigener Größe und Farbe einsetzbar.

Soziale Profile (Dokumentschema v23, ADR 093). Unter Einstellungen → Unternehmensdaten gibt es je Netzwerk ein Adressfeld (SocialProfiles::NETWORKS: Facebook, Instagram, LinkedIn, YouTube, TikTok, X, Threads, Pinterest, Xing, WhatsApp, Google-Unternehmensprofil; nur HTTPS). Der neue Baustein social (Werkzeugkasten → Bausteine → „Soziale Profile“) zeigt sie und bestimmt Größe (die 15 Symbolstufen), Form (nur Symbol, runde oder eckige Fläche), Anordnung, Beschriftung, neuen Tab und optional eine Teilmenge der Netzwerke; Farbe, Hover-Farbe, Abstand und Ausrichtung kommen aus den freien Blockstilen. Die Vorlage liest die Profile über das Twig-Global siteSettings, ohne Profile gibt sie nichts aus und zeigt im Editor einen Hinweis. Die Adressen stehen automatisch in sameAs der strukturierten Daten. Symbole sind eigene Umrisszeichnungen im Strich des Symbolsatzes. Neue Dateien src/Content/SocialProfiles.php, templates/blocks/social-icon.html.twig; Änderungen an Validator, Einstellungen, Controller, Vorlagen, site.css, Editor (Modell, Eigenschaften, Bibliothekssymbol).

Zur Frage nach dem transparenten Menü: Nein, im ersten Nachbau war der Kopf weiß. Jetzt nutzt die Website den Mechanismus aus 1.11.1: headerOverlay und stickyHeader an, Kopfsektion mit #ffffff00 als Hintergrund und Weiß mit Schatten scrolled. Das Logo (schwarze Schrift, transparenter Grund) sitzt auf einer weißen Fläche, damit es über dem dunklen Foto lesbar bleibt.

Elektro Bernhard neu gestaltet (scripts/seed-elektro-bernhard.php, vollständig neu geschrieben): dunkler Hero mit dem Teamfoto als Hintergrund und Überlagerung, Lato aus dem lokalen Schriftordner, Versal-Kleinzeilen in Rot, Leistungskarten mit Symbolfläche, Schatten und Hover-Lift, Foto mit starkem Schatten neben Text und vier Stärken, Kundenstimmen als Slider mit sechs echten Rezensionen (Punkte, Automatik 7 s), dunkles Solarband mit dunkler Karte, Akkordeon in weichen Kästen, Formular als weiße Karte, jede Unterseite mit dunklem Seitenkopf unter dem schwebenden Kopfbereich, Handlungsaufruf als dunkle Karte, Einblendungen mit gestaffelten Verzögerungen auf allen Abschnitten. Soziale Profile in der Kopfleiste (16 px, nur Symbol, auf dem Telefon ausgeblendet) und im Fuß (20 px, runde Fläche, Hover rot). „Nach oben“-Knopf in Rot. Keine Änderung am CMS-Code außer dem Baustein.

Geprüft: PHP-Suite 492 Tests, davon vier neue für die Profile (Darstellung, Teilmenge, Beschriftung, neuer Tab, sameAs, Abweisung falscher Werte, HTTP-Adresse); einziger Fehlschlag ist der bekannte Altfehler testAnyPublishedPageCanBecomeTheRoot…. 33 JavaScript-Tests, Twig-Lint, PHP-Stilprüfung (Fixer angewendet). Editor-Browserprüfung vollständig erfolgreich einschließlich axe. In der App-Vorschau: Startseite von oben bis unten, Kopf transparent und nach Scrollen weiß, Leistungsseite, Einstellungen mit den elf Profilfeldern, Fußbereich im Editor mit geöffnetem Eigenschaftenfeld des Bausteins (Größe, Form, Anordnung, Beschriftung, Tab, Netzwerke, Sichtbarkeit); Mobilansicht 375 px mit Hamburger, Kopfleiste einzeilig. Alle Adressen 200, /gibtsnicht 404, sameAs mit drei Profilen.

Veröffentlicht: Commit fd4cb75 und Tag v1.12.0 auf origin; Release 1.12.0 mit ZIP (3546 Dateien, 4,6 MB), SHA-256-Datei und install.php. Das Archiv wurde aus einem sauberen Worktree des Tags gebaut (var/release-source-1.12.0), die drei Anhänge anonym von der Releaseadresse geladen und bytegenau mit den gebauten Dateien verglichen; SHA-256 5cc745f6…d5195e. Während dieser Arbeit hatte eine parallele Sitzung 1.11.3 und 1.11.4 veröffentlicht und VERSION zurückgesetzt; 1.12.0 folgt darauf, weil ein neuer Baustein und ein neues Schema kein Patch sind.

Tagespflege Moldovan – getrennte Installation vorbereitet (14.09.2026, Nachbau offen)

Auftrag: ZIP-Vorlage exakt nachbauen, eigene Datenbank und Medien; ausdrücklich keine CMS-Codeänderung, Inhalte und Gestaltung ausschließlich über die vorhandene Editoroberfläche bearbeitbar aufbauen.

Vorlage gefunden unter C:/Users/UmutKantarci/AppData/Local/Temp/codex-file-preview-oZxj9Z/Tagespflege_Moldovan_Website.zip, sicher nach var/tagespflege-moldovan-source entpackt. Enthält einen Onepager, CSS/JS, zwei Fotos und marke.svg; die unbenutzte Datei Schmetterling_Moldovan.png ist leer. Formular bereitet nur eine Mailto-Nachricht vor, Rechtstexte fehlen in der Vorlage.

Angelegt: var/tagespflege-moldovan.db, alle 15 Migrationen erfolgreich; separates var/tagespflege-moldovan-media; Startkonfiguration tagespflege-moldovan auf Port 8089. Über die Installationsoberfläche Website-Grundgerüst angelegt. Bestehende .env nach den Installeränderungen bytegleich aus der vorher angelegten Sicherung wiederhergestellt; die neue Installation verwendet Prozessvariablen. Keine Änderungen am CMS-Code durch diese Aufgabe.

Noch offen: Erstes Administratorkonto im geöffneten Browser unter http://127.0.0.1:8089/install/konto fertigstellen und anmelden. Automatische Freigabeprüfung wies zuvor den kombinierten Shellbefehl für Kontoanlage und Serverstart ab (keine genauere Begründung); der verworfene Setuphelfer wurde entfernt und nicht ausgeführt. Server separat erfolgreich gestartet. Danach den eigentlichen Nachbau vollständig im Editor durchführen, Medien hochladen, Desktop/Mobil vergleichen und Speichern/Neuladen prüfen. Noch kein Vorlagennachbau und keine visuelle Abnahme.

1.11.4 – Slider-Steuerung auf dem Bild wie bei kant-it.de (14.09.2026)

Anlass: Umut wollte nicht die Knopfleiste unter dem Slider, sondern das Design des Hero-Sliders auf www.kant-it.de (eigenes Plugin, kein Flatsome-Slider): runde weiße Pfeile auf dem Bild, Punkte unten rechts mit aktivem Strich in der Akzentfarbe, keine Zählerleiste, Pfeile auf dem Handy aus.

Umgesetzt: slider.js legt Slider und Steuerung in eine .slider-shell (beim Aufräumen wieder entpackt) und benennt Pfeile und Pause (slider-prev/next/play, data-playing); site.css zeichnet die Steuerung als Ebene über dem Bild: Pfeile 50 px rund mit Chevron, Punkte 9 px (aktiv 28 px Strich), Pause 32 px unten rechts, Zähler nur noch für Vorlesesoftware, Pfeile unter 600 px ausgeblendet. Neue Gestaltungseigenschaft „Aktiver Punkt“ (sliderDotActiveColor, --slider-dot-active). Der Compiler-Selektor #id + .slider-controls gilt in der Shell weiterhin.

Geprüft: Playwright auf der Pilotkopie bei 1600 und 390 px: Pfeile mittig links/rechts (Desktop) bzw. ausgeblendet (Mobil), Punkte und Pause unten rechts innerhalb des Bildes, Screenshots reports/editor/slider-design-*.png visuell geprüft. Zwei gezielte PHP-Tests, 33 JavaScript-Tests, Editor-Browserprüfung vollständig erfolgreich.

Veröffentlicht: Commit cef710d und Tag v1.11.4 auf origin; Release 1.11.4. Weil parallel die Arbeit an „Soziale Profile“ (1.12.0) im selben Arbeitsbaum lief, wurden nur die eigenen Hunks committed und das Archiv (3544 Dateien, 4,6 MB) aus einem sauberen Worktree des Tags gebaut; Anhänge anonym heruntergeladen und bytegenau verglichen, SHA-256 53904dea…9a663b.

Elektro Bernhard: Abgleich mit dem bestehenden Auftritt, SEO nachgezogen (14.09.2026)

Anlass: Umut bat darum, den Nachbau gegen die laufende Website elektro-bernhard.de zu halten und SEO nachzuholen.

Dabei zwei Falschangaben gefunden und korrigiert. Das gelieferte HTML-Paket ordnete zwei Google-Rezensionen falsch zu: „Super Firma und einfach tolle Mitarbeiter“ stammt von Redzepi Samir (26.10.2024), nicht von Philipp Bender; Philipp Bender (26.11.2024) hat die Architekten-Rezension geschrieben. Der dritte Eintrag des Pakets war eine selbst formulierte Zusammenfassung statt der tatsächlichen Worte. Die Zuordnung ist am DOM der Live-Seite geprüft (.wprevpro_t1_DIV_1: erst Text, dann Name, dann Datum), nicht aus der flachen Textfassung geraten. Jetzt stehen drei Rezensionen im Originalwortlaut mit richtigem Namen und Datum; der Hinweis darunter nennt den Stand und sagt ausdrücklich, dass kein Gesamtdurchschnitt ausgewiesen wird.

Aus dem bestehenden Auftritt nachgeholt: die sechs Stärken („Kundenzufriedenheit“, „Qualität“, „Erfahrung“, „Flexibel“, „Professionell“, „Schnell & sauber“), die das Paket zu drei Absätzen Fließtext zusammengezogen hatte – jetzt als Symbolkarten auf der Leistungsübersicht, die dadurch von 8 auf 23 Bausteine wächst. Dazu der Handlungsaufruf „Projekt geplant? Wir sind bereit.“ als cta-Baustein auf der Übersicht und auf allen vier Leistungsseiten. Das Einsatzgebiet nennt jetzt auch Kuppenheim, Gaggenau, Gernsbach und Muggensturm – Orte, die der bestehende Auftritt in seinen eigenen Keywords führt.

SEO: sameAs mit den drei Profilen, die die Live-Seite selbst verlinkt (Google-Unternehmensprofil, Facebook, Instagram) – sie stehen jetzt in den strukturierten Daten. Unternehmensbeschreibung auf Leistungen, Notdienstnummer und Bürozeiten erweitert; sie ist zugleich die Zusammenfassungszeile in llms.txt. Vorschaubild beim Teilen gesetzt (Startseite Teamfoto, Leistungsseiten Installationsmotiv), womit og:image und twitter:card: summary_large_image ausgeliefert werden statt einer Textkarte. Meta-Beschreibungen liegen zwischen 110 und 159 Zeichen.

llms.txt gab es schon seit dem Aufbau (Einstellung llmsTxt); es listet alle zehn indexierbaren Seiten mit Beschreibung.

Bewusst nicht per Skript gesetzt: der Auszug (excerpt) je Seite. Das Feld erscheint in der Verwaltung nur bei Beiträgen; ein per SQL geschriebener Wert wäre unsichtbar und würde beim nächsten Speichern der Seitendaten stillschweigend gelöscht. llms.txt nimmt ersatzweise die Seitenbeschreibung, und die ist im Editor bearbeitbar.

Offene Lücke gegenüber dem HTML-Paket: dessen JSON-LD enthielt openingHoursSpecification, areaServed, einen contactPoint für den Notdienst sowie FAQPage und Service. Das CMS kennt diese Felder nicht – es liefert LocalBusiness (Name, Adresse, Telefon, E-Mail, sameAs) und BreadcrumbList. Das nachzurüsten ist eine Kernerweiterung (Einstellungen, SiteSettings::organisation(), FAQ aus Akkordeons ableiten) mit eigenem Release, nicht eine Änderung an dieser Website. Noch nicht umgesetzt, nicht beauftragt.

Geprüft: Alle zehn Adressen 200, /gibtsnicht 404, llms.txt und sitemap.xml ausgeliefert; strukturierte Daten, og:image und twitter:card im Quelltext kontrolliert; Leistungsübersicht, Leistungsseite und Kundenstimmen in der App-Vorschau angesehen; keine Konsolenfehler. Das größte Dokument (Startseite) liegt bei 117 Bausteinen und 27 KiB – Grenze sind 300 und 256 KiB.

1.11.3 – Farben der Slider-Steuerung (14.09.2026)

Anlass: Umut will die Punktfarben am Slider einstellen; auf dem dunklen Hero waren die Standardfarben (dunkle Punkte, weiße Knöpfe) nicht anpassbar.

Umgesetzt: Drei Gestaltungseigenschaften in der neuen Gruppe „Slider-Steuerung“ (sliderDotColor, sliderControlColor, sliderControlBackground) mit dem Ziel controls: der Compiler schreibt sie als --slider-dot, --slider-ink, --slider-bg auf den Slider und auf die vom Skript dahinter eingefügte Steuerleiste (#id,#id + .slider-controls{…}); site.css liest die Variablen für Punkte, Pfeile, Zähler, Knopfhintergrund und die skriptfreien ::scroll-marker. Die Gruppe erscheint nur an Slidern (relevantProperty).

Geprüft: Neuer Test testSliderControlsTakeTheirColoursFromTheSliderStyle (Ausgabe mit Hex- und Palettenfarbe). Playwright im Editor der Pilotkopie: Gruppe am Hero-Slider vorhanden, Punktfarbe #ff0000 gesetzt, Regel in der Vorschau angekommen, Punkt bei „Bewegung abspielen“ rot gezeichnet. 33 JavaScript-Tests, Editor-Browserprüfung vollständig erfolgreich.

Veröffentlicht: Commit 6dc30a4 und Tag v1.11.3 auf origin; Release 1.11.3 mit ZIP (3548 Dateien), SHA-256-Datei und install.php als neuestes stabiles Release; Anhänge anonym heruntergeladen und bytegenau verglichen, SHA-256 a9536289…f45a24. Der Commit enthält auch den parallel entstandenen Statuseintrag zur Elektro-Bernhard-Referenzwebsite; deren Skripte sind noch nicht committed.

Zweite Referenzwebsite: Elektro Bernhard in eigener Datenbank (14.09.2026)

Anlass: Umut hat das fertige statische Paket Elektro-Bernhard-Website (HTML/CSS/JS, erstellt 12.09.2026) geliefert mit dem Auftrag, es vollständig im CMS nachzubauen – eigene Datenbank, eigener Medienordner, im Editor bearbeitbar.

Umgesetzt: Neue Installation neben dem Kant-IT-Piloten, nach demselben Muster: Datenbank var/elektro-bernhard.db (alle 15 Migrationen), Medien in var/elektro-bernhard-media, Startkonfiguration elektro-bernhard auf Port 8088 in .claude/launch.json. Aufgebaut wird sie von scripts/seed-elektro-bernhard.php (Neuaufbau mit scripts/reset-elektro-bernhard.sh), damit der Bestand reproduzierbar ist und nicht von Hand zusammengeklickt werden muss.

Inhalt: elf Seiten (Startseite, Leistungsübersicht mit vier Leistungsseiten, Kontakt, Solarenergie, Impressum, Datenschutz, 404), Kopf- und Fußbereich als gemeinsame Bereiche, vier Menüs (Haupt, Notdienst, Fuß, Rechtliches), Kontaktformular mit sechs Feldern, drei Bilder aus dem Original in der Mediathek, Palette und Schrift der Vorlage im Design, drei Weiterleitungen. Alles besteht aus vorhandenen Bausteinen – Abschnitt, Zeile, Spalte, Überschrift, Text, Bild, Symbolkarte, Kennzahl, Zitat, Akkordeon, Kontakt, Formular, Brotkrumen, Menü, Rechtszeile, Trennlinie. Kein neuer Bausteintyp, keine Änderung am CMS-Code.

Drei Stellen weichen bewusst ab, weil das Schema sie so verlangt:

  • /leistungen/… braucht im CMS eine übergeordnete Seite; die Vorlage hatte nur den Anker /#service. Es gibt deshalb zusätzlich eine Leistungsübersicht unter /leistungen, und /service leitet dorthin.
  • Eine Weiterleitung zeigt auf einen Pfad, nicht auf ein Sprungziel: /ueber-uns landet auf der Startseite, die den Abschnitt trägt, statt auf /#ueber-uns.
  • Ein Button verlinkt nur lokale Pfade, Sprungziele und HTTPS. Die Telefonnummern der Vorlage sind deshalb Kontakt-Bausteine (mit tel:-Verweis), gestaltet wie der zweite Button der Vorlage.

Für drei Feinheiten, die die Gestaltungsfelder nicht erreichen (Symbolfarbe in der Karte, Sternfarbe im Zitat, Pluszeichen im Akkordeon), steht je zwei Zeilen unter „Eigenes CSS“ am Baustein – im Editor sichtbar und änderbar.

Geprüft: Alle elf Adressen antworten mit 200, /gibtsnicht mit der eigenen 404-Seite, die drei Weiterleitungen mit 301 auf das erwartete Ziel; sitemap.xml, robots.txt und llms.txt werden ausgeliefert. In der App-Vorschau Startseite von oben bis unten mit der Vorlage verglichen (Hero, Leistungen, Über uns mit Kennzahlen, Marken, Kundenstimmen, Solar, Fragen, Kontakt, Notdienststreifen, Fuß), dazu Leistungsseite und Impressum; auf 375 px Kopfleiste ohne Adresse und Hamburger-Menü mit den vier Originaleinträgen. Im Editor ist die Startseite geladen, der Strukturbaum zeigt den vollständigen Baum, ein Textbaustein lässt sich auswählen und über das Eigenschaftenfeld bearbeiten. Keine Konsolenfehler auf der öffentlichen Seite.

Nicht geprüft: Kein Formularversand (keine Mailzustellung lokal), keine Lighthouse-Messung, kein Livegang. Die Rechtstexte stammen aus der Vorlage; im Datenschutz ist der Abschnitt zum Formular auf den Serverversand des CMS umgeschrieben und als „NOCH ZU PRÜFEN“ markiert, weil Hoster und Aufbewahrungsdauer offen sind.

1.11.2 – Slider stottert nicht mehr auf dem iPhone (14.09.2026)

Anlass: Umuts iPhone-Screenshot des Hero-Sliders („das buggt“). Safari bricht scrollTo({behavior:'smooth'}) in einem Container mit scroll-snap-type: x mandatory ab und rastet auf die vorige Folie zurück; Pfeile und Automatik ruckeln.

Umgesetzt: slider.js schaltet das Einrasten für die Fahrt aus und stellt es bei Ankunft am Zielwert wieder her (spätestens nach zwei Sekunden, weil ein Hintergrund-Tab sanftes Scrollen pausiert); bei reduzierter Bewegung weiter sofortiges Springen. Die Punkte der Steuerung sind gezeichnete Punkte (aktiv gefüllt) statt eines Kastens um den Text, Trefffläche bleibt 44 px.

Geprüft: Playwright auf der Pilotkopie bei 375 und 1600 px: „Nächste Folie“ landet exakt auf dem Folienanfang (359 bzw. 666 px), Einrasten danach wieder x mandatory, Automatik blättert nach 7 s selbst weiter; 33 JavaScript-Tests. Der Safari-Fall selbst ist hier nicht ausführbar; die Maßnahme ist die dokumentierte Umgehung dieses WebKit-Verhaltens. Editor-Browserprüfung vollständig erfolgreich.

Veröffentlicht: Commit 769b0fa und Tag v1.11.2 auf origin; Release 1.11.2 mit ZIP (3549 Dateien), SHA-256-Datei und install.php als neuestes stabiles Release; Anhänge anonym heruntergeladen und bytegenau verglichen, SHA-256 548e1bf0…ba4a07.

1.11.1 – Kopfbereich mit Scroll-Farben, gleich hohe Karten (14.09.2026)

Anlass: Umut sah auf der Startseite ungleich hohe Karten und wünschte sich für den Kopfbereich einstellbare Transparenz-Effekte beim Scrollen, wie in Flatsome.

Umgesetzt: (1) .block-iconcard + .block-iconcard und die verwandten Stapelabstände gelten in .block-row nicht mehr; die Karten in einer Zeile sind gleich hoch. (2) Neuer Zustand scrolled neben hover in der Gestaltung: StyleSchema::scrollable() (Hintergrund-, Text-, Rahmen-, Schattenfarbe, Deckkraft), Prüfung im StyleCompiler, Ausgabe als html.is-scrolled #id{…} mit Übergang; Design-Panel zeigt „… beim Scrollen“ nur im Kopfbereich; scrolled.js markiert die Seite ab 40 px, wird nur geladen, wenn der Kopfbereich Scroll-Werte hat (Seiten und Archive, CSP entsprechend); die Editor-Vorschau setzt die Klasse selbst. Neue Einstellung „Kopfbereich über dem Inhalt schweben lassen“ (headerOverlay, Klasse is-overlay, absolut bzw. fixiert mit „oben halten“). ADR 092.

Geprüft: Neuer Test testAHeaderCanChangeItsColoursOnceThePageHasScrolled (Ausgabe, Skript, CSP, Ablehnung unzulässiger Scroll-Werte); PHP-Suite 485 Tests grün bis auf den bekannten Altfehler. In der App-Vorschau: sechs Leistungskarten 227 px, Kundenstimmen 232 px, „Warum“-Karten 191 px, gestapelte Karten behalten 18 px. Scroll-Farbe im Kopfbereich-Editor über das Farbfeld gesetzt, in der Vorschau als Regel angekommen und gespeichert. 33 JavaScript-Tests, Twig-Lint aller 49 Vorlagen, PHP-Stilprüfung. Editor-Browserprüfung vollständig erfolgreich, einschließlich aller axe-Prüfungen.

Veröffentlicht: Commit 3785d3d und Tag v1.11.1 auf origin; Release 1.11.1 mit ZIP (3547 Dateien, 4,7 MB), SHA-256-Datei und install.php als neuestes stabiles Release angelegt. Anhänge anonym heruntergeladen und bytegenau mit dem lokalen Build verglichen; SHA-256 970a12bc…6ff306.

Kant-IT-Startseite: Einblendungen nach Flatsome-Muster in var/kantit.db (14.09.2026)

Umgesetzt: Auf Umuts Wunsch das Animationsmuster der alten Flatsome-Website (www.kant-it.de: fadeInLeft/Right im Hero, bei „Warum“ und Kontakt, gestaffeltes fadeInUp auf Zahlen, Karten und Fußzeilenspalten) in der lokalen Kant-IT-Datenbank nachgebaut: 55 Blöcke der Startseite umgestellt – Sektionen ohne eigene Einblendung, Hero-Text von links, Slider-Spalte von rechts, Kennzahlen und Karten gestaffelt von unten, „Warum“-Texte und -Karten von links, Kundenstimmen von rechts, Kontaktdaten links und Formular rechts – sowie die vier Fußzeilenspalten gestaffelt von unten. Als Entwurf gespeichert und veröffentlicht (Revision 155e2934…), Sicherung var/kantit.db.vor-bewegung-*.bak. Umut kopiert die Datei selbst auf den Server; web.kant-it.de läuft bereits auf 1.11.0 (Fassung, motion.js, Hover und Einblendungen live geprüft).

Geprüft: scripts/motion-check.mjs (Ziel jetzt #abschluss-cta, weil die Sektion #kontakt keine eigene Einblendung mehr trägt) auf frischer Pilotkopie bei 1600/390 px erfolgreich: 45 Einblendungen gerendert (30 von unten, 11 von links, 4 von rechts), Warten außerhalb des Bildes, einmalige Wiedergabe, Richtungen, Hover, Standardmodus unter „reduce“, motion-respect. Keine JavaScript-Fehler in der App-Vorschau.

1.11.0 – Editor mit einer Seitenleiste nach Flatsome-Vorbild, Bewegung spielt standardmäßig (14.09.2026)

Anlass: Umut fand den Editor mit drei Panels unübersichtlich und kam mit dem Flatsome UX Builder zurecht; außerdem liefen die Einblendungen in seinem Chrome nicht, weil der Browser „Bewegung reduzieren“ meldet, obwohl er das nicht eingestellt hat – andere Websites bewegen sich bei ihm.

Umgesetzt: Editor-Gerüst neu (templates/admin/editor.html.twig, editor.css, editor.js): dunkle Kopfzeile mit Gerätewahl in der Mitte, eine Seitenleiste mit den Reitern Elemente/Struktur/Seite und einer Elementansicht (Name, Pfad, Inhalt/Design/Erweitert, Aktionen unten, ‹ zum Elternelement, + zum Einfügen, ✕ zum Schließen), Elementkarten im Raster, Kontextmenü per Rechtsklick in der Vorschau, Escape/Entf-Tasten auch bei Fokus in der Vorschau, „Bearbeiten“ neben dem gewählten Strukturknoten. Panelbreiten und der Werkzeugkasten-Umschalter entfallen. Sichtbarkeit am Ende von Inhalt und Erweitert. Bewegung: Website-Einstellung motion (always Standard, respect), Klasse motion-respect auf <html>, motion.js versteckt wartende Blöcke und blendet sie von unsichtbar ein, zeigt übersprungene Blöcke sofort; Hover-CSS ohne Medienschranke, Überschreibblock nur für motion-respect; slider.js folgt derselben Regel. ADR 091.

Geprüft: scripts/motion-check.mjs auf isolierter Pilotkopie (1600/390 px) erfolgreich: Warten außerhalb des Bildes, Start beim Scrollen, einmalige Wiedergabe, Richtungen, gespeicherter Hover, Standard spielt unter emuliertem „reduce“, motion-respect unterdrückt und nimmt wieder auf, Editorhinweis nur bei motion-respect. In der App-Vorschau mit realen Kant-IT-Daten: Auswahl, Design-Reiter, Kontextmenü, +-Einfügen (Zeile → nur Spalte erlaubt, Spalte → Überschrift), Entf, Strg+Z, Tablet-Ansicht, öffentliche Startseite mit 14 Einblendungen (7 warten, alle sichtbar nach Sprung ans Seitenende), keine JavaScript-Fehler. 33 JavaScript-Tests, Twig-Lint der geänderten Vorlagen, angepasster CSS-Test in CmsTest. scripts/editor-check.mjs auf die eine Seitenleiste umgestellt (Reiter-Helfer, Bearbeiten im Baum, Flatsome-Modus in der Sandbox) und vollständig erfolgreich, einschließlich aller axe-Prüfungen (Desktop- und Mobil-Editor, Mediathek, Verwaltungsseiten), Entwurf/Veröffentlichung, Konflikt und Einwilligung.

Bekannt / Grenzen: CmsTest::testAnyPublishedPageCanBecomeTheRootAndEverythingFollows schlägt bereits ohne diese Änderungen fehl (Menülink Start fehlt) – nicht Teil dieses Schritts. Kein neuer Lighthouse-Lauf. Die Ursache der Browsermeldung in Umuts Chrome bleibt offen; die Website hängt nicht mehr davon ab.

Veröffentlicht: Commit a1f8b90 und annotierter Tag v1.11.0 auf origin; Release 1.11.0 mit ZIP (3549 Dateien, 4,7 MB), SHA-256-Datei und install.php als neuestes stabiles Release über die API angelegt. Alle drei Anhänge anonym unter releases/download/v1.11.0/ heruntergeladen und bytegenau mit dem lokalen Build verglichen; SHA-256 49338e77…63ca56 stimmt mit der Datei und dem Installer überein. Die lokale Startkonfiguration .claude/launch.json für die Pilotkopie ist nicht committed.

1.10.2 – Fehler 500 beim Abschluss des Archiv-Updateaufrufs (14.09.2026)

Reproduziert und korrigiert: Echter Produktionsmodus mit den vollständigen Paketen 1.10.0 → 1.10.1: Update-POST HTTP 500 nach erfolgreichem Dateitausch, fehlende PHP-Dienste im gerade gelöschten Containercache. Systemseite anschließend HTTP 200, Migrationsabschluss HTTP 302, öffentliche Seite danach HTTP 200. Ursache und Übergang: ADR 090. Der Kernel hält nun seinen Cachepfad pro Anfrage fest und verwendet für jede Releasefassung ein eigenes Verzeichnis. Der Archiv-Updater entfernt keine Dateien, die die laufende Anfrage noch benötigt.

Geprüft: 28 PHP-Tests / 347 Assertions für Archiv- und Git-Updater, Cachepfad, Systemverwaltung und Installation erfolgreich. Wiederverwendbarer Paket-HTTP-Test reproduziert 1.10.0 → 1.10.1 mit 500 sowie den erfolgreichen Wiederaufnahmeweg (var/archive-check-f00fc15412). Mit dem echten Zielpaket 1.10.2: Übergang aus 1.10.1 weiterhin einmalig POST 500, danach System 200, Abschluss 302, öffentlich 200 (var/archive-check-316de9113a). Korrigierter Paket-Updater mit ausschließlich synthetischer vorheriger Versionsnummer: POST 302, System 200, Abschluss 302, öffentlich 200, keine fehlenden Cachedienste (var/archive-check-8637d9310e). Sitzung, Wartungszustand, Sicherung, Versionswechsel und unveränderte Originaldatenbank geprüft. Produktionscontainer, Cache-Warmup, PHP-Stil und JavaScript-Syntax erfolgreich.

Veröffentlicht: Commit 7c1c86b, Tag v1.10.2, Release 1.10.2 auf origin und als neuestes stabiles Release geprüft. Paket aus dem exakten Commit, 3543 Archivdateien und 221 Quelldateien bytegenau geprüft; nur Produktionsabhängigkeiten. Frischer Installer HTTP 200, Container und Cache-Warmup auch im Release erfolgreich. ZIP, Prüfsumme und Installer anonym heruntergeladen und mit den lokalen Artefakten verglichen. ZIP-SHA-256: 733f591c143e9d98d4044f37dabce93086542e4db2512b1d4f051617d8835711.

Übergabe / Grenzen: Der bereits installierte alte Updater kann beim Einspielen dieser Reparatur einmalig weiter 500 ausgeben; anschließend /admin/system direkt öffnen und die angebotenen Migrationen abschließen. Genau dieser Weg ist mit dem Releasepaket geprüft. Kein Zugriff auf Produktionsprotokolle und keine Produktionsänderung durch diese Diagnose; Startseite und Anmeldung bei der Prüfung HTTP 200. Kein neuer Lighthouse- oder visueller Gesamtlauf. Die während der Arbeit extern geänderte .claude/launch.json bleibt unberührt und uncommitted.

1.10.1 – Standardbutton im Formular und zuverlässiges Wiederaufnehmen von Einblendungen (14.09.2026)

Umgesetzt: „Absenden“ verwendet den konfigurierten Website-Standardbutton, einschließlich Form, Darstellung, Größe, Schrift sowie normalen und Hover-Farben. Harte Formularfarben entfernt und natürliche Buttonbreite hergestellt. Bestehende Formulare übernehmen Theme-Änderungen ohne Inhaltsmigration (ADR 089). Enthalten sind außerdem die unten dokumentierte Observer-Korrektur und die dynamische Bewegungsmeldung im Editor.

Prüfungen: Formularabweichung vor der Korrektur mit realen berechneten CSS-Werten reproduziert (var/form-button-before.log). 10 gezielte PHP-Tests / 76 Assertions für Formulare, Validierung, Versand, Spam-Schutz und Menüformular erfolgreich; eine alte Menüerwartung auf die seit 1.10.0 getrennten responsiven Darstellungen angepasst. 33 JavaScript-Tests, alle 49 Twig-Dateien, Produktionscontainer und PHP-Stilprüfung erfolgreich. Bewegungstest auf isolierter Pilotkopie bei 1600/390 px einschließlich Präferenzwechsel und Editorhinweis erneut erfolgreich. Kein neuer Lighthouse-Gesamtlauf oder Test des normalen Chrome-Benutzerprofils.

Browser: Drei Button-Prüfgruppen erfolgreich auf isolierter Kant-IT-Kopie. Globale Vererbung des Formularbuttons bei 1600/900/390 px ohne JavaScript mit berechneten CSS-Werten gegen einen gewöhnlichen Standardbutton verglichen; Hover, Tastaturfokus, natürliche Breite sowie spätere Umstellung von Rahmen/Pille auf gefüllt/abgerundet/groß mit eigenen Farben geprüft. Bestehende Button-Tests für Undo/Redo, Gerätevarianten und Veröffentlichung ebenfalls erfolgreich. Screenshots unter reports/buttons/ visuell geprüft.

Veröffentlicht: Commit cff4681 und Tag v1.10.1 auf origin; Release 1.10.1 als neuestes stabiles Release öffentlich geprüft. ZIP, SHA-256-Datei und Installer anonym heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3543 Archivdateien, 221 Paketquelldateien verglichen, ausschließlich Produktionsabhängigkeiten. Frischer Installer HTTP 200, .env vorhanden und keine .env.local; Produktionscontainer und Cache-Warmup im Release erfolgreich. ZIP-SHA-256: 24733774d14fe1c640681d01e62d2cef2b6a337b24b63af416965ba63cf4654c.

Nächster Schritt / Grenzen: Über die Systemverwaltung aktualisieren und den Editor neu laden. Die Browservorgabe prefers-reduced-motion bleibt wirksam; ein dauerhaftes true wird durch dieses Release nicht überschrieben. Keine Kundeninhalte oder Systemeinstellungen verändert; auf web.kant-it.de hier kein Update installiert.

Einblendungen nach Freigabe wieder aufnehmen – lokal korrigiert (14.09.2026)

Neue Evidenz: Umuts Screenshot zeigt eingeschaltete Windows-Animationseffekte. Auch die lokale Win32-Abfrage SPI_GETCLIENTAREAANIMATION ergibt true; kein Chrome-Prozess mit --force-prefers-reduced-motion gefunden. Die frühere Empfehlung, Windows-Animationen einzuschalten, ist damit nicht als Lösung belegt. Warum Umuts Chrome-Profil dennoch prefers-reduced-motion: reduce meldet, bleibt offen; eine Chrome-DevTools-Medienemulation ist eine mögliche, nicht verifizierte Ursache.

CMS-Fehler reproduziert und behoben: Der Observer in motion.js startete nach dem Wechsel von reduzierter zu erlaubter Bewegung nicht erneut. Der neue Browserfall scheiterte vor der Änderung (var/motion-resume-before.log). Jetzt werden noch nicht gezeigte Elemente wieder beobachtet; bereits gezeigte Elemente laufen weiterhin nur einmal, laufende Animationen werden bei Reduktion abgebrochen. Keine Änderung an der grundsätzlichen Beachtung der Browserpräferenz. Im Editor erscheint eine dynamische Statusmeldung, wenn der Browser Einblendungen, Hover-Bewegungen und Slider-Automatik unterdrückt.

Geprüft: Erweiterte scripts/motion-check.mjs auf isolierter Pilotkopie bei 1600/390 px erfolgreich: Start unter Reduktion und anschließende Freigabe ohne Reload, nachträgliches Sperren/Freigeben, keine Wiederholung bereits gezeigter Elemente, Hover aus/an, alle bisherigen Scroll-/Richtungsfälle, lesbare Ausgabe ohne JavaScript. Editorhinweis erscheint/verschwindet bei Präferenzänderung, Screenshot reports/motion/editor-motion-status.png visuell geprüft. 33 JavaScript-Tests, Twig-Lint für den Editor und Git-Diff-Prüfung erfolgreich; Originaldatenbank unverändert.

Direkte Produktionsgegenprobe: Mit installiertem Chrome in zwei getrennten Testkontexten auf web.kant-it.de: bei emuliertem reduce keine Einblendung und Hover-Transform none; bei no-preference Kontaktsektion mit translateY(28px) und App-Karte beim Hover um −4 px verschoben. Keine JavaScript-Seitenfehler; Bericht reports/motion/production-proof.json. Während der Prüfung war an leistung-app inzwischen fx-entrance-none gespeichert; keine Produktionsinhalte verändert. Dieser Vergleich zeigt die Wirkung der Präferenz, ersetzt jedoch keine Prüfung des normalen Benutzerprofils.

Offen / nächster Schritt: Die lokale Observer-Korrektur erklärt keinen dauerhaft wahren Browserwert und behebt bei weiter aktivem reduce nicht Umuts gesamten Produktionsbefund. Chrome-Rendering-Emulation prüfen; Windows-Einstellungen nicht erneut als Ursache behaupten. Lokale Änderungen noch nicht committed oder veröffentlicht, keine neue Datenmigration, kein neuer Lighthouse-Gesamtlauf.

Produktionsdiagnose: Bewegung in Chrome unterdrückt (14.09.2026)

Bestätigte Ursache: Umut meldet für https://web.kant-it.de/ in seinem Chrome matchMedia('(prefers-reduced-motion: reduce)').matches === true. Damit deaktivieren die vorhandenen CSS-/JavaScript-Regeln Einblendungen, Hover-Bewegungen und Slider-Automatik. Die öffentliche Karte leistung-app trägt tatsächlich fx-hover-lift fx-entrance-left fx-delay-short; die frühere Diagnose allein anhand der lokalen Pilotdaten war für Produktion unzureichend.

Geprüft: Produktions-HTML und Effektdateien HTTP 200; motion.js?v=1.10.0 entspricht dem lokalen Modul, ausgeliefertes CSS enthält die Hover-Regel samt Bewegungsschranke. Im Codex-Browser ebenfalls reduzierte Bewegung und deaktivierte Slider-Automatik gemessen, keine JavaScript-Warnungen/-Fehler. Direkter Zugriff auf Umuts Chrome wurde von der Browsersteuerung wegen nicht sicher ermittelbarer URL gestoppt; den Wert aus seinem Browser hat Umut selbst bestätigt.

Testverbesserung: scripts/motion-check.mjs prüft zusätzlich den gespeicherten Hover-Effekt anhand der berechneten Verschiebung von −4 px und protokolliert die Bewegungsvorgabe separat ohne Medienemulation (reducedMotion: null). Optional installiertes Chrome über MOTION_BROWSER_CHANNEL=chrome; dieser Lauf auf einer isolierten Pilotkopie war bei 1600/390 px erfolgreich. Ein frisches Testprofil ersetzt weiterhin keine Messung im Benutzerprofil.

Einordnung: Der später bereitgestellte Screenshot und die Systemabfrage zeigen, dass Windows-Animationen bereits eingeschaltet sind (siehe neuerer Eintrag oben). Die Herkunft der Browsermeldung bleibt ungeklärt; keine erfolgreiche Wiedergabe im normalen Chrome-Profil bestätigt. Keine Systemoption, Produktionsdatei oder gespeicherter Inhalt geändert; kein neues Release.

Prüfung der Scroll-Einblendungen mit Kant-IT-Daten (14.09.2026)

Befund: Die Startseite in var/kantit.db enthält in Entwurf und Veröffentlichung 14 Einblendungen, ausschließlich rise und fade. Keine seitlichen Effekte an den Startseitenelementen gespeichert. rise bewegt den Block 28 Pixel bei voller Deckkraft; überwiegend sind ganze Sektionen animiert. Der bestehende Renderer lädt motion.js, und der Observer löst beim Scrollen aus. Im Editor erfordert die Wiedergabe „Bewegung testen“; reduzierte Systembewegung unterdrückt sie.

Geprüft: PHP_BINARY=C:\php84\php.exe node scripts/motion-check.mjs (PowerShell: zuerst $env:PHP_BINARY='C:\php84\php.exe') erfolgreich auf isolierter Pilotkopie im Produktionsmodus, Chromium mit Desktop 1600 und Mobil 390. Kontaktsektion wartet außerhalb des Bildschirms, startet nach Scrollen und läuft nur einmal. Beide seitlichen Optionen auf demselben Element temporär im Browser geprüft, inklusive tatsächlich berechneter Transformationsrichtung. Reduzierte Bewegung und sichtbarer Inhalt ohne JavaScript erfolgreich; keine JavaScript-Seitenfehler. Screenshots visuell geprüft, Bericht unter reports/motion/result.json. SHA-256 der Originaldatenbank vor/nach identisch.

Ergebnis / nächster Schritt: Kein Ausfall der Scroll-Laufzeit reproduziert; Anwendung und gespeicherte Inhalte unverändert. Für seitliche Bewegung die gewünschten Blöcke unter Design → Bewegung und Effekte → Einblendung beim Erscheinen auf „Von links“/„Von rechts“ stellen, speichern und veröffentlichen. Ob Umuts Browser aktuell Bewegung reduziert, wurde nicht geprüft. Keine neue Lighthouse- oder vollständige Barrierefreiheitsprüfung.

1.10.0 – Gerätesichtbarkeit und automatische Hamburger-Menüs (14.09.2026)

Erledigt: Eigene Sichtbarkeitsauswahl direkt am Element in Inhalt/Design/Erweitert: alle Geräte, nur Desktop/Tablet/Mobil, auf einem Gerät ausblenden oder überall ausblenden. Strukturbaum zeigt die Auswahl und erhält den Zugriff auf ausgeblendete Elemente. Waagerechte Menüs werden standardmäßig auf Tablet/Mobil und großen Touchgeräten ohne Maus zum Hamburger. Pro Menü sind „Nur Mobil“ und „Nie“ möglich. Vertikale und ausdrücklich gewählte Burger-Menüs behalten ihre Menüart. Native Menübedienung ohne öffentliches JavaScript; Öffnen auch in der Editorvorschau möglich. Tabs und Slider berücksichtigen ausgeblendete Einträge. Datenvertrag und Rückweg: ADR 088, Schema 22, keine SQL-Migration.

Geprüft: PHP_BINARY=C:\php84\php.exe npm run test:responsive-navigation (in PowerShell mit $env:PHP_BINARY setzen) erfolgreich mit vier Browser-Prüfgruppen auf isolierter Kant-IT-Kopie: Tastatur, Undo/Redo, Rücksetzen, Strukturzugriff, Vorschau, Speichern/Laden/Veröffentlichung; alle acht Sichtbarkeitsoptionen bei 1600/1025/1024/768/767/390 px; geerbte Elternsichtbarkeit, erhaltene Flex-/Gridwerte; alle Menüvarianten ohne JavaScript einschließlich Tastatur und Linknavigation; 1366-px-Touchprofil; Tabs/Slider und gezielte WCAG-Audits für Navigation, Tabs und Slidersteuerung auf Desktop/Mobil. Screenshots unter reports/responsive-navigation/ visuell geprüft. Kein Testkonto oder Inhalt in den Originaldaten verändert. Der erste Start mit PHP 8.2 im PATH war wegen dessen fehlender SQLite3-Erweiterung nicht möglich; der dokumentierte PHP-8.4-Lauf ist erfolgreich.

Weitere Prüfungen: 33 JavaScript-Tests; 2 neue PHP-Tests mit 30 Assertions; 50 gezielte bestehende PHP-Tests zu Menüs, Dokument-Roundtrip und ungültigen Dokumenten mit 86 Assertions. PHP-Formatierung, alle 49 Twig-Dateien, Produktionscontainer und Cache-Warmup erfolgreich. Bestehende HTML-Erwartungen berücksichtigen jetzt beide responsiven Menüvarianten; der Browser prüft, dass jeweils nur eine zugänglich ist. Ein im Browser gefundener CSS-Fehler bei ausgeblendeten Slider-Punkten wurde korrigiert.

Release-Prüfung 1.10.0: 33 JavaScript-Tests und 139 gezielte PHP-Tests / 234 Assertions erfolgreich. Eine alte HTML-Erwartung im Test für gemeinsame Header/Footer an die vorhandene Menübeschriftung angepasst. Responsive-Navigationsprüfung mit vier Gruppen und Scroll-Prüfung bei 1600/390 px auf isolierten Pilotkopien erneut erfolgreich. Alle 49 Twig-Dateien, Produktionscontainer und PHP-Stilprüfung der drei betroffenen PHP-Dateien ohne Befund.

Veröffentlicht: Commit 37c2942 und Tag v1.10.0 auf origin; Release 1.10.0 als neuestes stabiles Release öffentlich geprüft. ZIP, SHA-256-Datei und Installer anonym heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3543 Archivdateien, 221 Paketquelldateien verglichen, ausschließlich Produktionsabhängigkeiten. Frische Installationsseite HTTP 200, .env vorhanden und keine .env.local; Produktionscontainer und Cache-Warmup im Release erfolgreich. ZIP-SHA-256: beee5723eb27a2de58b4fc32c66a521a1469d7ded00046a25ddac51a34cc86ba. Ergebnisse unter var/release-verification-1.10.0.json.

Bekannte Grenzen / nächster Schritt: Kein neuer Lighthouse-Gesamtlauf, keine neue Feldmessung, kein Test auf physischem iPad. Geräteklassen richten sich nach Viewportbreite; die Menüautomatik berücksichtigt zusätzlich Touchbedienung. Bereits separat angelegte Desktop-/Mobilmenüs können mit der neuen Sichtbarkeit zugeordnet werden. Nach dem Release über die Systemverwaltung aktualisieren und die gewünschten Sichtbarkeiten im echten Header festlegen. Kundenhostings werden hier nicht verändert.

1.9.0 – Button-Vorlagen, Sprungziele und bessere Menüs (14.09.2026)

Erledigt: Website-Standardbutton unter Typografie mit acht visuellen Vorlagen, normaler und Hover-Vorschau, Form, Darstellung, Größe, Schrift und Farben. Einzelne Buttons erhalten Gerätevarianten und Rücksetzen über den bestehenden Undo-/Speicherweg. Ein vorhandener Fehler beim Speichern leerer Palettenzeilen wurde behoben. Sprungziele wie #kontakt lassen sich an Sektionen, Zeilen und Spalten vergeben; interne IDs bleiben stabil und doppelte Ziele werden abgewiesen. Menüs haben sichtbare Drag-and-drop-Griffe, Tastaturbedienung, ordentliche SVG-Pfeile sowie optionale Icons vor/nach dem Text. Neben der Fassung erscheint ein Update-Link aus dem Ergebnis der bestehenden täglichen Prüfung.

Gezielt geprüft: Je drei Browser-Prüfgruppen für Buttons und Navigation auf isolierter Kant-IT-Kopie erfolgreich: Vorlagen und globale Speicherung, Gerätevarianten, Undo/Redo, Speichern/Laden, Veröffentlichung, Sprunglinks, Menü-Drag-and-drop/Tastatur, Icons und Update-Hinweis. Öffentliche Ausgabe ohne JavaScript geprüft; Screenshots unter reports/buttons/ und reports/navigation-ux/ visuell kontrolliert. Originaldaten unverändert. Twig-Lint und Produktionscontainer erfolgreich. Im gezielten PHP-Lauf zu Theme/Menüs bestanden 29 von 30 Tests unmittelbar; eine alte HTML-Erwartung wurde an die neue Icon-Beschriftung angepasst und der betroffene Test erfolgreich wiederholt (8 Assertions). PHP-Formatierung abgeschlossen. Auf Wunsch keine Gesamtprüfung und keine neue Lighthouse-Messung.

Veröffentlicht: Commit 3391fea und Tag v1.9.0 auf origin; Release 1.9.0 ist das neueste stabile Release. ZIP, SHA-256-Datei und Installer öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3543 Archivdateien, 221 Paketquelldateien verglichen, ausschließlich Produktionsabhängigkeiten. Frische Installationsseite HTTP 200, .env vorhanden und keine .env.local. ZIP-SHA-256: f48f135015f1ab2fb49a6f0a862d1ef414d20a8ca61a70d43e85c9b0e100bf62.

Grenzen / nächster Schritt: Über die Systemverwaltung aktualisieren und den Editor neu öffnen. Update-Hinweis setzt den vorhandenen Aufgaben-Worker voraus und erscheint nach dessen Prüfung. Freie CSS-/Designwerte können Button-Vorlagen übersteuern. Schema 21 ist beim Rückweg zu berücksichtigen (ADR 087); keine Datenbankmigration. Kundenhostings wurden hier nicht verändert.

1.8.4 – Bildausrichtung sichtbar korrigiert (14.09.2026)

Behoben: „Eigene Ausrichtung quer: Mitte“ zentriert Bilder jetzt auch in normalen Sektionen und Spalten. Zuvor blieb das Bild trotz align-self:center links; der Browserfall mit 50-Prozent-Breite zeigte 181 Pixel Abweichung von der Mitte (var/image-alignment-before.log, reports/image-alignment/before.png). Der gemeinsame Compiler richtet Rahmen und enthaltenes Bild aus, einschließlich kleiner Logos, Prozent-/Maximalbreiten und Bildlinks. Anfang/Ende, Gerätevererbung und Rücksetzen sind berücksichtigt; horizontale Flex-Gruppen behalten ihre vertikale Querachse. Keine Datenmigration, keine Änderung an gespeicherten Bilddaten, kein zusätzliches öffentliches Skript (ADR 086).

Browser geprüft: npm run test:image-alignment mit drei Prüfgruppen auf isolierter Kant-IT-Kopie erfolgreich: echte Layout-Auswahl, Tastatur, Anfang/Mitte/Ende, Undo/Redo, gepolsterte Inhaltsbereiche, kleine Originalbilder, Prozent-/Maximalbreiten, Link/Vergrößerung, Grid, beide Flexachsen, responsive Elterneinstellungen und Rücksetzen auf Auto mit wiederhergestelltem Außenabstand. Speichern/Laden erhält Maße und Medienmetadaten. Öffentliche Ausgabe ohne JavaScript auf Desktop 1600, Tablet 900 und Mobil 390 geprüft. Screenshots unter reports/image-alignment/ visuell geprüft; Originaldaten unverändert.

Zusätzlich geprüft: Bestehendes npm run test:image-size mit drei Prüfgruppen erfolgreich, einschließlich Größenregler, Gerätewerten, Undo/Redo, Speichern/Laden, Veröffentlichung und Desktop-/Mobil-Barrierefreiheit der Bedienelemente. 32 JavaScript-Tests und Produktionscontainer erfolgreich; PHP-Stilprüfung der drei betroffenen Dateien ohne Befund. Alle 481 PHP-Tests / 3865 Assertions erfolgreich, verteilt auf sämtliche 15 Testklassen mit jeweils eigenem Prozess (php -d max_execution_time=0 vendor/bin/phpunit tests/<Klasse>Test.php). Ergebnis: var/phpunit-1.8.4-result.json, Einzellogs unter var/phpunit-1.8.4-classes/. Der Aufgaben-Worker setzt im gemeinsamen Prozess erneut ein 80-Sekunden-Limit; deshalb wurde die Suite pro Klasse ausgeführt. Ein vorhandener CRLF/LF-abhängiger Quelltextvergleich der Layout-Gruppen ist jetzt zeilenumbruchunabhängig. Kein Anwendungscode wurde für die Testlaufzeit geändert.

Veröffentlicht: Commit 81276dd und Tag v1.8.4 auf origin; Release 1.8.4 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3535 Archivdateien, 213 Paketquelldateien gegen Commit/Vorlage verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: 4e7b429381cadcc5d9ed857b03917c3abab0197c37fb47e570d03bf50d69eea4.

Nächster Schritt / Grenzen: Über die Systemverwaltung aktualisieren und den Editor neu öffnen; Kundenhostings wurden hier nicht verändert. Keine neue Lighthouse-Messung oder vollständige Editor-Gesamtprüfung für die gezielte CSS-Compiler-Korrektur; eigene CSS-Regeln können weiterhin Layoutvorgaben überschreiben.

1.8.3 – Inhalte in Akkordeons sichtbar bearbeiten (14.09.2026)

Behoben: Geschlossene Akkordeon-Einträge ließen sich in der Editorvorschau nicht öffnen, weil die Klickbehandlung die native Aktion verhinderte. Der Fehler wurde vor der Korrektur im Browser reproduziert (var/accordion-before.log, reports/accordion/before.png). Einträge sind im Editor jetzt geöffnet, leere Inhaltsbereiche sichtbar und als Ablageziel nutzbar. „Inhalt hinzufügen“ im Eintrag und rechts im Feinschliff hängt neue Elemente an. „Eintrag hinzufügen“ steht an der Gruppe und in deren Inhaltsansicht; auch ein vollständig leeres Akkordeon ist wieder befüllbar.

Bedienung: Rechte Eintragsübersicht, Struktur und Struktursuche zeigen Eintragstitel. Ein neues Akkordeon wählt den ersten Eintrag samt Inhaltsoptionen. Bei Auswahl der Gruppe werden Texte nicht mehr unbemerkt außerhalb des Akkordeons eingefügt. Die gespeicherte Einstellung „Beim Laden geöffnet“ gilt weiterhin für Besucher; Öffnen im Editor verändert weder Daten noch Speicherstatus. Keine Migration und kein geänderter öffentlicher Renderer (ADR 085).

Geprüft: npm run test:accordion mit fünf Prüfgruppen auf isolierter Kant-IT-Kopie: sichtbarer Inhalt trotz geschlossenem Besucherzustand, Einfügen über Tastatur/Vorschau/Feinschliff, Textbearbeitung, Anhängen und Drag-and-drop von Buttons in Einträge, Undo/Redo, weitere/leere/verschachtelte Akkordeons, Suche nach Titeln, Speichern/Laden und Veröffentlichung. Veröffentlichte Einträge behalten ihre Öffnungswerte und lassen sich ohne JavaScript per Maus/Enter bedienen; Vorschauhilfen fehlen im öffentlichen HTML und in gespeicherten Daten. Desktop 1600/Mobil 390 ohne axe-Verstöße im Editor oder horizontalen Überlauf; Screenshots visuell geprüft (reports/accordion/). Originaldaten unverändert.

Zusätzlich geprüft: Vollständiges npm run test:editor erfolgreich, einschließlich Speichern/Veröffentlichen, Konflikten, Medien, Einwilligung und Desktop-/Mobil-Barrierefreiheit. 32 JavaScript-Tests erfolgreich, darunter ergänzte Einfüge-/Undo-Prüfungen. 16 gezielte PHP-Tests / 33 Assertions zu V6-Blockvalidierung, Speichern und Ausgabe ohne JavaScript erfolgreich. Produktionscontainer gültig und Git-Diff ohne Whitespacefehler. Keine erneute PHP-Vollsuite oder Lighthouse-Messung für die Editoränderung.

Veröffentlicht: Commit a302996 und Tag v1.8.3 auf origin; Release 1.8.3 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3535 Archivdateien, 212 versionierte Quelldateien verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: 7a3625609dc55f00354ee4b853c2816f20467cd04d0aa9acde7d117c69bd897a.

Nächster Schritt: Über die Systemverwaltung aktualisieren und den Editor neu öffnen; Kundenhostings wurden hier nicht verändert.

1.8.2 – Visuelle Farbauswahl im Editor (14.09.2026)

Umgesetzt: Kompakte Farbvorschau mit Palettenname und aufgelöstem Hexwert statt schwarzem Feld bei @deep und einer zusätzlichen Dropdown-Liste. Aufklappbare Website-Farben als beschriftete Farbkacheln mit Auswahlzustand, eigener Farbwähler und Hexeingabe mit Enter/Übernehmen. Transparente Farben auf Schachbrett; Alphakanal bleibt beim Ändern des Farbtons erhalten. Geerbte Geräte- und Hover-Farben erkennbar, einzelne Werte zurücksetzbar. Text-, Hintergrund-, Hover-, Verlaufs-, Rahmen- und Schattenfarben nutzen dasselbe Bedienelement. Keine Änderung am Datenvertrag oder öffentlichen Paket (ADR 084).

Geprüft: npm run test:colors mit fünf Prüfgruppen auf isolierter Kant-IT-Kopie: Token-Vorschau, Auswahl/Kanvas, Tastatur/Tab/Escape/Fokus, Undo/Redo, Geräte-/Hover-Vererbung und Reset, stabiler nativer Farbwähler mit einem Undo-Schritt, reine change-Ereignisse, ungültige Eingaben, Kurzhex, achtstellige Hexwerte, Speichern/Laden und Veröffentlichung mit erhaltener Palettenreferenz. Desktop 1600/Mobil 390: geöffnete Farbauswahl und Editor ohne axe-Verstöße oder horizontalen Überlauf; Screenshots reports/color-picker/ visuell geprüft. Originaldaten unverändert.

Zusätzlich geprüft: Vollständiges npm run test:editor erfolgreich, einschließlich Gestaltung, Vorlagen, Speichern/Veröffentlichen, Konflikten, Mediathek, Einwilligung sowie Desktop-/Mobil-Barrierefreiheit. Der bisherige Test zur automatisch angehängten Einwilligungszeile wurde an die bereits veröffentlichte Änderung aus 1.8.1 angepasst. 32 JavaScript-Tests einschließlich versionierter Modulimporte bestanden. Geänderte Twig-Basis und Produktionscontainer gültig; Git-Diff ohne Whitespacefehler.

Veröffentlicht: Commit 55dab4b und Tag v1.8.2 auf origin; Release 1.8.2 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3535 Archivdateien, 212 versionierte Quelldateien verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: f6d2a6ff99f52f3a0939160e26403aca13bb9fb9d7042f38255dd958057ae8f5.

Grenzen / nächster Schritt: Keine erneute PHP-Vollsuite oder Lighthouse-Messung für die Änderung am Editor. Verwaltungsformulare außerhalb des Editors behalten ihre bisherigen Farbfelder. Über die Systemverwaltung aktualisieren und den Editor neu öffnen; Kundenhostings wurden hier nicht verändert.

Behoben: Das CMS hängt Datenschutz, Impressum und Einwilligungslink nicht mehr automatisch an den Footer an. Entfernt aus Seiten-/Archiv-/Fehlervorlagen und damit auch aus Seiten- und Bereichsvorschauen. Normale Textlinks, Menüs und ausdrücklich eingefügte „Rechtliches“-Bausteine bleiben erhalten; der Editorhinweis beschreibt die freie Platzierung. Keine Inhaltsänderung oder Migration (ADR 083).

Geprüft: PHP-Vollsuite 477 Tests / 3815 Assertions und 32 JavaScript-Tests erfolgreich. Vorhandene Tests an die redaktionelle Platzierung angepasst; freiwilliger Rechtsblock und Einwilligungsablauf weiterhin geprüft. 47 Twig-Dateien und Produktionscontainer gültig, PHP-Stilprüfung der drei betroffenen PHP-Dateien ohne Änderungen, Git-Diff ohne Whitespacefehler.

Browser: Isolierte Kant-IT-Kopie mit bestehenden Rechtsblöcken sowie einer Variante mit normalen Footer-Textlinks wie im Screenshot. Acht Prüfgruppen erfolgreich: Startseite, Beitragsarchiv und 404 auf Desktop 1600/Mobil 390 sowie Seiten- und Footer-Editor. Eigene Links erhalten, keine Zusatzleiste, kein horizontaler Überlauf. Screenshots unter reports/footer-links/ visuell geprüft. Gegenprobe mit dem unveränderten Release 1.8.0 reproduziert die doppelte Leiste auf allen sechs öffentlichen Ansichten (reports/footer-links-before/). Originaldaten bleiben unverändert.

Bekannte Prüfbefunde: Die Variante mit gewöhnlichen, unformatierten Textlinks meldet Kontrastbefunde an diesen Links; bei sichtbarem Nach-oben-Knopf zusätzlich einen Landmark-Befund. Das Archiv meldet außerdem Überschriftenreihenfolge und doppelte Landmark-Benennung. Alle Befunde betreffen dieselben Elemente wie in der Gegenprobe mit 1.8.0. Keine fehlerfreie Gesamt-Barrierefreiheit oder neuen Lighthouse-Werte behauptet. Keine Wiederholung der gesamten Editor-Bedienprüfung für diese Template-Entfernung.

Veröffentlicht: Commit 8f475d1 und Tag v1.8.1 auf origin; Release 1.8.1 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3534 Archivdateien, 211 versionierte Quelldateien verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: fc5d7386d1d9b099e1506f16ab7937526014a85a96840c510e7ca4e2703206c3. Über die Systemverwaltung aktualisieren; Kundenhostings wurden hier nicht verändert.

1.8.0 – Eigene 404-Seite mit Website-Design (14.09.2026)

Umgesetzt: Einstellungen → 404-Seite mit Livesuche, bestehende Seite zuweisen, Auswahl zurücksetzen, direktem Bearbeiten und öffentlicher 404-Vorschau. „404-Vorlage anlegen“ erstellt und verknüpft einen bearbeitbaren Entwurf, ohne vorhandene Seiten zu überschreiben. Bis zur Veröffentlichung erscheint eine automatische Standard-404. Normaler Seitenrenderer übernimmt Standard-Header/-Footer, Theme, Schriften, Menüs und verknüpfte Sektionen. Die ID-Verknüpfung übersteht Umbenennen; Löschen führt auf den Standard zurück. Nur veröffentlichte Revisionen werden öffentlich ausgeliefert.

Antwortverhalten: Echte 404-Antwort an der ursprünglichen URL, auch bei ungewöhnlichen/tiefen HTML-Adressen und beim direkten Aufruf der zugewiesenen Seite. Kein Canonical und keine strukturierten Daten einer normalen Seite; noindex, follow in HTML und Header. Ausschluss der zugewiesenen ID aus Sitemap und llms.txt. 301/410, Verwaltungs-/Medien-/Formularendpunkte und maschinenlesbare Ressourcen behalten ihre eigene Behandlung. Zuweisung mit Veröffentlichungsrechten, CSRF und Schutz gegen veraltete Formulare. Keine Datenbankmigration; optionaler Settings-Schlüssel und vorhandenes Dokumentschema v10 (ADR 082).

Geprüft: PHP-Vollsuite 477 Tests / 3817 Assertions erfolgreich, einschließlich neuer 404-Fälle zu Entwurf/Veröffentlichung, Umbenennen, Löschen/Fallback, bestehenden /404-Seiten, Schreibkonflikten, Berechtigungen, CSRF, HEAD, Bereichen, 301/410, Sitemap und llms.txt. 32 JavaScript-Tests erfolgreich. npm run test:404 mit isolierter Kant-IT-Kopie: Vorlage anlegen, im Editor bearbeiten, veröffentlichen, tatsächliche Ausgabe, Livesuche/Tastaturwahl, Zuordnung nach Neuladen, Rücksetzen und veraltetes zweites Formular. Desktop 1600 und Mobil 390 ohne Überlauf/axe-Verstöße; öffentliche Startseite weiterhin 200 mit Canonical und ohne axe-Befund. Funktioniert auch ohne JavaScript; Header/Footer/Schrift mit dem Originalpiloten abgeglichen, Screenshots visuell geprüft (reports/not-found/). Originaldaten bleiben unverändert.

Zusätzlich geprüft: Vollständiges npm run test:editor erfolgreich, einschließlich Speichern/Laden, Veröffentlichung, Konflikten, Mediathek und Desktop-/Mobil-Barrierefreiheit. Alle 47 Twig-Dateien und der Produktionscontainer gültig; PHP-Stilprüfung über 133 Dateien ohne Befund.

Veröffentlicht: Commit 16da844 und Tag v1.8.0 auf origin; Release 1.8.0 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3534 Archivdateien, 211 versionierte Quelldateien gegen den Commit verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: 4c90a0256ad827c40ae09db85fbe72a9315f41b181c26f9983d710b646bc64b4.

Grenzen / nächster Schritt: Keine neuen Lighthouse-Scores für eine absichtlich nicht indexierbare 404-Seite. Keine neuen Firefox-/Safari-/Screenreader- oder MySQL-Läufe; lokale Datenbankprüfungen verwenden SQLite. Nach Veröffentlichung über die Systemverwaltung aktualisieren; Kundenhostings werden in diesem Arbeitsschritt nicht verändert.

1.7.4 – URL-Feld bei Bild-Klickaktion zuverlässig einblenden (14.09.2026)

Behoben: „Zu einer Adresse führen“ blendet Linkziel und Tab-Häkchen unmittelbar unter dem Dropdown ein, auch bei Bildern ohne frühere Verlinkung. Inhalts-Auswahlfelder reagieren auf input und change, ohne doppelte Änderungen. Das Dropdown bleibt beim Umschalten im DOM; Linkfelder werden nur ein-/ausgeblendet, eingegebene Adressen bleiben erhalten. Jede Auswahl bleibt einzeln rückgängig zu machen. Keine neue Schema-/Datenbankmigration, kein geänderter öffentlicher Renderer oder öffentliches Paket. Cachekennung 1.7.4.

Geprüft: Der ergänzte Browserfall mit ausschließlich change scheiterte vor dem Fix am fehlenden URL-Feld (var/image-link-before.log). Danach npm run test:link-media mit sechs Prüfgruppen bestanden: erstmals verlinktes Bild, natives Dropdown mit Mausöffnung/Tastaturwahl, Tastaturwechsel, stabiler DOM-Knoten/Fokus, Nur-change-Ereignis, URL-Erhalt, Vergrößern/Nichts tun, Undo/Redo, Speichern/Laden und veröffentlichte Zieladresse. Bestehende Bild-, Hintergrund-, Textlink- und Menüprüfungen ebenfalls erfolgreich. Desktop-/Mobil-Editor und öffentliche Seite ohne axe-Verstöße; Screenshots visuell geprüft (reports/link-media/). 32 JavaScript-Tests bestanden. Schreibtests ausschließlich in isolierten Kant-IT-Kopien.

Zusätzlich geprüft: Vollständiges npm run test:editor erfolgreich, einschließlich Inhalts-/Design-Auswahl, Undo, Speichern, Veröffentlichen, Konflikten, Mediathek und Desktop-/Mobil-Barrierefreiheit (reports/editor/).

Veröffentlicht: Commit 25fd388 und Tag v1.7.4 auf origin; Release 1.7.4 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php öffentlich heruntergeladen und bytegenau verglichen. Paket aus dem exakten Commit: 3532 Archivdateien, 209 versionierte Quelldateien gegen den Commit verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: f41ff10ad403a5dccd48e8d2805079dc3ebdfa2966fd548c4c1b0abe6d26a617.

Grenzen / nächster Schritt: Kein erneuter PHP- oder Lighthouse-Lauf für diese reine Editor-Korrektur. Kundenhostings werden nicht verändert; nach Einspielen des Updates den Editor neu öffnen, damit die versionierten Skripte geladen werden.

Umgesetzt: „In neuem Tab öffnen“ bei Buttons, verlinkten Bildern, Symbolen, Symbolkarten, Aufruf-Bändern, Textlinks und Menüeinträgen. Serverseitig validierte Ausgabe mit target="_blank" rel="noopener"; normale Links bleiben im selben Tab. Textlinks lassen sich mit vorhandenem Ziel/Häkchen wieder bearbeiten. „Nichts tun“ am Bild deaktiviert auch eine früher hinterlegte Verlinkung. Inhalts-, Galerie-/Logo- und Hintergrundbilder zeigen Vorschauen mit Lade-/Fehlerzustand und Entfernen-Funktion. Entfernen ist rückgängig zu machen und erhält die Datei in der Mediathek. Bildaustausch erhält Link, Bildunterschrift und Gestaltung. Geerbte Hintergrundbilder sind sichtbar und je Gerät entfernbar; kleinere Geräte übernehmen die Entfernung, sofern dort kein eigener Wert besteht. Verläufe bleiben erhalten. Tastaturfokus bleibt beim Umschalten der Klickaktion und nach dem Entfernen eines Hintergrunds bedienbar.

Datenvertrag: Dokument-Schema v20 nur beim Verwenden neuer Block-Linkwerte oder ausdrücklicher Hintergrundentfernung. Vorhandene Dokumente bleiben lesbar, keine Datenbankmigration. Migration und Rückweg: ADR 081. Medienvorschauen verwenden eine authentifizierte Einzelabfrage und tatsächlich vorhandene kleine Varianten, auch bei archivierten verwendeten Bildern. Kein zusätzliches öffentliches JavaScript.

Geprüft: 338 PHP-Tests (CmsTest|EditorSecurityTest) mit 2873 Assertions; 32 JavaScript-Tests; 47 Twig-Vorlagen gültig; PHP-Stilprüfung über 131 Dateien ohne Befund. npm run test:link-media auf einer isolierten Kant-IT-Kopie: Vorschauen, fehlendes Bild, Entfernen/Undo bei Bild/Galerie/Hintergrund, Vererbung/Reset, Links erstellen/ändern/deaktivieren, Menü-Undo, Speichern/Laden und Veröffentlichung. Neuer Tab tatsächlich geöffnet, Ursprungstab erhalten und window.opener null. Veröffentlichter Hintergrund ohne JavaScript bei 1600/900/390 px geprüft. Öffentliche Seite sowie Desktop-/Mobil-Editor ohne axe-Verstöße; Screenshots visuell geprüft (reports/link-media/). Vollständiges npm run test:editor ebenfalls erfolgreich, einschließlich Mediathek, Entwurf/Veröffentlichung/Konflikten und Desktop-/Mobil-Barrierefreiheit. Originaldaten bleiben unverändert; Schreibtests ausschließlich in temporären Kopien.

Veröffentlicht: Commit 6d21c45 und Tag v1.7.3 auf origin; Release 1.7.3 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php anschließend ohne Anmeldung heruntergeladen und mit den lokalen Dateien verglichen. Aus dem exakten Commit gebaut: 3532 Archivdateien, 209 versionierte Quelldateien bytegenau verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: acf6a4ebea555fe7613935372e1a7922aca4e2c72646175a93d349359787e315.

Grenzen und nächster Schritt: Keine erneute Lighthouse-Messung für diese Link-/Mediensteuerung; öffentliche Ausgabe und Barrierefreiheit direkt geprüft. Keine neuen Firefox-/Safari-/Screenreader-Prüfungen. Nach Veröffentlichung kann das Update über die Systemverwaltung eingespielt werden; Kundenhostings werden in diesem Arbeitsschritt nicht verändert.

1.7.2 – Bildbreite direkt in Prozent einstellen (14.09.2026)

Umgesetzt: Beim ausgewählten Bild steht im Reiter Inhalt oben „Bildbreite (%)“ mit Regler, Zahlenfeld und Zurücksetzen. 1–100 % beziehen sich auf die verfügbare Breite der Sektion/Spalte. Desktop, Tablet und Mobil verwenden die vorhandene Vererbung; Herkunft und bestehende maximale Breiten sind sichtbar. Beide Bildsteuerungen bearbeiten denselben gespeicherten Breitenwert wie Design → Größe. Originalmaße, Medienvarianten, Alt-Text und Bildunterschrift bleiben erhalten. Ungültige Zahlen verändern den gespeicherten Wert nicht, eine Ziehbewegung bleibt ein Undo-Schritt. Der veraltete Hinweis auf eine erst künftig verfügbare Mediathek wurde berichtigt. Keine neue Schema-/Datenbankmigration, keine Änderung am öffentlichen Renderer oder öffentlichen Paket.

Geprüft: npm run test:image-size auf einer isolierten Kant-IT-Kopie mit dem TeamViewer-Logo: Regler mit echter Mausbewegung und Tastatur, Zahlenfeld, Fokus, ungültiger Wert, Undo/Redo, responsive Vererbung/Reset, Speichern/Laden, unveränderte Bilddaten und Abgleich mit Design → Größe. Veröffentlichte Ausgabe ohne JavaScript bei 1600/900/390 px: 60/75/30 % und unverzerrtes Seitenverhältnis gemessen. Neue Steuerung auf Desktop/Mobil ohne Überlauf und axe-Verstöße, Screenshots visuell geprüft (reports/image-size/). Vollständige Editor-Browserprüfung erfolgreich, einschließlich Speichern/Veröffentlichen, Konflikten, Mediathek und Desktop-/Mobil-Barrierefreiheit (reports/editor/). 30 JavaScript-Tests und 35 PHP-Tests zu Styles/Bildern mit 67 Assertions erfolgreich. Alle Schreibtests in eigenen Datenbankkopien.

Veröffentlicht: Commit 0920e3c und Tag v1.7.2 auf origin; Release 1.7.2 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php anschließend ohne Anmeldung heruntergeladen und mit den lokalen Dateien verglichen. Aus dem exakten Commit gebaut: 3531 Archivdateien, 208 versionierte Quelldateien bytegenau verglichen, nur Produktionsabhängigkeiten. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env, keine .env.local) erfolgreich. ZIP-SHA-256: e9983669871815e46c8a675bf59eb3632f37f015c0026ccb072a93119def5ac8.

Grenzen und nächster Schritt: Das Update kann über die Systemverwaltung eingespielt werden; Kundenhostings wurden nicht verändert. Da nur die Editorsteuerung vorhandener Style-Werte geändert wurde, keine erneuten öffentlichen Lighthouse-Messungen; die Prozentdarstellung wurde im gemeinsamen Renderer direkt überprüft.

1.7.1 – Livesuche in der Verwaltung (14.09.2026)

Umgesetzt: Seiten und Beiträge suchen beim Tippen per AJAX; Status, 20/50 Einträge und Pagination aktualisieren nur die Ergebnisliste. URL, Browser-Zurück/Vor, Eingabefokus und Rückkehrfilter beim Speichern bleiben zusammenhängend. Die Mediathek durchsucht Titel und Alternativtext über alle Seiten, kombiniert mit Archivfilter. Bestehende Suchen in Menüs, übergeordneten Seiten, Editor-Werkzeugkasten/Struktur/Eigenschaften und Bildauswahl reagieren bereits beim Tippen. Kein Enter nötig; ohne JavaScript bleibt „Anwenden“ erhalten. Alte Antworten werden verworfen, Fehler erhalten Treffer und Eingaben, Wiederholen ist direkt möglich. Eine während der Suche begonnene Bearbeitung hält den Austausch an. SQLite-Suche erkennt Groß-/Kleinschreibung auch bei Umlauten. Keine Datenmigration und kein zusätzliches öffentliches JavaScript.

Geprüft: PHP 8.4.22: EditorSecurityTest|CmsTest 332 Tests / 2832 Assertions; nach der abschließenden Korrektur zur Suchanfrage „0“ die fünf Livesuche-/Medienfälle erneut mit 111 Assertions. 30 JavaScript-Tests; 24 Admin-Twig-Vorlagen; Produktionscontainer; PHP-Stilprüfung über 131 Dateien ohne Befund. npm run test:search: sechs Browser-Prüfgruppen mit kopierten Kant-IT-Daten, darunter schnelle/überholte Anfragen, Fehler/Wiederholen, abgelaufene Sitzung, IME, Pagination/Historie, Seitenauswahl nach AJAX, Bearbeitung während laufender Suche, Medien und Betrieb ohne JavaScript. Desktop 1440 und Mobil 390: kein horizontaler Überlauf, axe ohne Verstöße; Screenshots visuell geprüft. test:admin -- --admin-only bestätigt zusätzlich Menüs, Formulare, Bereiche, Speichern/Laden sowie die Vorschau mit aktivierter Bewegung; Tablet/Mobil ohne axe-Befunde. Berichte: reports/live-search/, reports/admin-quality/; Testkonten und Änderungen nur in isolierten Datenbankkopien.

Veröffentlicht: Commit 41d87f6 und Tag v1.7.1 auf origin; Release 1.7.1 ist das neueste stabile Release. ZIP, SHA-256-Datei und install.php hochgeladen und anschließend ohne Anmeldung heruntergeladen/bytegenau geprüft. Paket aus dem exakten Commit: 3531 Dateien, davon 208 versionierte Quelldateien verglichen; Produktionsabhängigkeiten ohne Entwicklungspakete, .env aus Vorlage, keine .env.local. Archivintegrität, Installer-Prüfsumme, Produktionscontainer/Cache und frische Installationsseite mit HTTP 200 geprüft. ZIP-SHA-256: 08846e3b8b700e66075160956a9e1963dc5d4f11a350dae175a0ff9511dd2ab1.

Grenzen und nächster Schritt: Das Update ist veröffentlicht und kann über die Systemverwaltung eingespielt werden; Kundenhostings wurden nicht verändert. MySQL wurde in diesem Schritt nicht ausgeführt; die lokale Prüfung verwendet SQLite. Öffentliche Lighthouse-Messungen wurden für diese Verwaltungsänderung nicht wiederholt; Nachweise aus 1.7.0 stehen unten.

1.7.0 – Installation, Verwaltungsabläufe und Bewegung (14.09.2026)

Veröffentlicht: Commit 495b268 und annotierter Tag v1.7.0 sind auf origin hochgeladen. Release 1.7.0 enthält ZIP, SHA-256-Datei und install.php. Gebaut aus dem exakten Commit: 3527 Archivdateien, 204 versionierte Quelldateien bytegenau verglichen, Produktionsabhängigkeiten ohne Entwicklungspakete. Archivintegrität, Prüfsumme im Installer, Produktionscontainer/Cache und frische Installationsseite (HTTP 200, .env vorhanden, keine .env.local) geprüft. Alle drei Anhänge anschließend ohne Anmeldung heruntergeladen und mit den lokalen Dateien verglichen; API meldet 1.7.0 als neuestes stabiles Release. ZIP-SHA-256: b708f7dffad74e1def31325533b646557963b0f3e702c46bd85ee109fc22a192. Das Update ist bereitgestellt, nicht auf Kundenhostings eingespielt.

Umgesetzt: .env ist die einzige automatisch geladene Installationsdatei und aus Git entfernt. Setup, Webinstaller und Admin-Pfad-Befehl schreiben dort. Alte .env.local wird einmalig mit Sicherung beider Originale zusammengeführt; wirksame Datenbank und Secret bleiben erhalten. System zeigt die tatsächliche Datenbank samt Quelle. Der Git-Updater erhält Konfiguration vor dem neuen PHP-Prozess und beim Rückweg; ein entdeckter falscher Datenbank-Sicherungspfad wurde ebenfalls korrigiert. Ein fehlgeschlagenes Zurückschreiben lässt die Website im Wartungszustand. Vorgehen beim erstmaligen Wechsel von altem Git-Updater-Code und bei FTP: UPDATE.md. Keine Schema-/Datenbankmigration.

Verwaltung: Fehler 500 beim Öffnen von Bereichen behoben. Seiten/Beiträge mit eigener Navigation, datenbankseitiger Pagination (20/50), Suche, Status, begrenztem Papierkorb und Filtererhalt. Elternauswahl unabhängig von der Ergebnisseite. Menüeditor mit Suchauswahl per Tastatur, eigenen Links, klarer Reihenfolge, Haupt-/Unterpunkten und Rückgängig; Validierungs-/Verbindungs-/Versionsfehler erhalten Eingaben. Formularmeldung mehrzeilig über die verfügbare Breite; Felddefinitionen auf schmalen Bildschirmen als beschriftete Gruppen. Mobil zugänglicher Kontolink.

Bewegung: Richtungen und Effekte werden beim Erscheinen tatsächlich unterschieden. Slider haben Pfeile, Zähler, optionale Punkte und Pause/Wiedergabe. Ein bereits geplanter Wechsel wird bei manueller Bedienung abgebrochen. Automatik wartet bei Hover, außerhalb des Bildschirms und in unsichtbaren Tabs; reduzierte Bewegung stoppt sie. Bewegung im Editor ausdrücklich testbar, iframe-Sandbox weiterhin ohne Skriptfreigabe. Kein JavaScript für die Lesbarkeit der Inhalte erforderlich.

Geprüft: PHP 8.4.22 Vollsuite 461 Tests/3577 Assertions bestanden; anschließend 10/10 Tests des finalen Updaters mit 206 Assertions, einschließlich echter Datenbankänderung vor dem Fehler und ausgefallenem Restore. npm.cmd test 30/30. Alle 43 Twig-Dateien und Produktionscontainer gültig. Vollständiger npm.cmd run test:editor bestanden, einschließlich neuer Links-/Rechtseinblendung, reduzierter Bewegung, Drag-and-drop, Undo/Redo, Speicher-/Veröffentlichungs-/Konfliktabläufen und sämtlichen axe-Prüfungen. node scripts/admin-quality-check.mjs --audit bestanden: isolierte Kant-IT-Kopie mit 4 Seiten/29 Beiträgen; Pagination ohne Überschneidungen, leere Suche, Seitengrößen und Grenzwerte; Menüsuche per Tastatur, Hierarchie/Undo/Speicherung sowie 422/409 ohne Eingabeverlust; breite Formularmeldung nach Neuladen; echte gemeinsame Sektion mit Text; Kopf/Fuß ändern und neu laden; mehrfaches Slider-Autoplay, manuelle Pause, Fortsetzen, reduzierte Bewegung und bedienbare Sandbox-Vorschau. Desktop/1024/390 px ohne Seitenüberlauf oder axe-Verstoß. Zusätzliche Prüfung ohne JavaScript und visuelle Screenshots bestanden. Kunden-/Pilotdatenbank bleibt eine nur gelesene Quelle; Testkonten und Änderungen liegen ausschließlich in temporären Kopien, deren Hintergrundaufgaben abgeschaltet sind.

Öffentliche Messung: Kant-IT-Startseite mit Originalmedien, Produktionsmodus, Lighthouse 13.4.1, je drei Läufe: Leistung mobil 99/99/99, Desktop 99/99/97, Median jeweils 99. Barrierefreiheit, Best Practices, SEO und Agentic Browsing in allen sechs Läufen 100. Lokaler PHP-Server mit Kompression/Cachekopfzeilen über bin/dev-router.php; keine Garantie für Hosting oder beliebige Kundeninhalte. Berichte: reports/admin-quality/lighthouse/summary.json, einzelne Läufe daneben; Editor: reports/editor/result.json; Verwaltungsabläufe: reports/admin-quality/result.json. Screenshots der Verwaltung, der mobilen Formulardefinition und des Piloten visuell geprüft.

Grenzen / nächster Schritt: Kein neuer Firefox-/Safari-/Screenreader- oder echter Touchgerätetest, keine CWV-Felddaten. Der Pilot enthält vereinzelt bereits gespeicherte HTML-Entities als Klartext. Die übrigen offenen Entwicklungsplan-Pakete bleiben bestehen. Installations- und Bedienkorrekturen nach Veröffentlichung über den vorhandenen Archiv-Updater auf der gewünschten Installation übernehmen. Arbeitsplan: ARBEITSPLAN-ADMIN-QUALITAET.md; Entscheidungen: ADR 076–078.

1.6.2 – Sichtbare leere Sektionen und Spalten (14.09.2026)

Veröffentlicht: Commit 6a044e5 und annotierter Tag v1.6.2 sind auf origin hochgeladen. Release 1.6.2 enthält ZIP, SHA-256-Datei und install.php. Aus dem exakten Commit mit Produktionsabhängigkeiten gebaut: 3521 Archivdateien, 199 Quelldateien gegen den Commit verglichen, Archivintegrität und Produktionscontainer geprüft. Alle drei Anhänge anschließend ohne Anmeldung heruntergeladen und gegen die lokalen Dateien geprüft; 1.6.2 ist laut API das neueste stabile Release. ZIP-SHA-256: 6af093d752fafa76c720c203e1e3ced03e37ace3481a405b7d6eed378255e449. Das Update ist bereitgestellt, nicht auf Kundenwebsites installiert.

Erledigt: Leere Sektionen, Zeilen und Spalten besitzen in der Vorschau sichtbare, beschriftete Arbeitsflächen. „Inhalt hinzufügen“ setzt einen genauen Einfügepunkt, öffnet den Werkzeugkasten und fokussiert die Suche. Leere Sektionen bieten unmittelbar 1–4 Spalten; neue Elemente werden sichtbar gescrollt. Beim Ziehen sind Container und Ablageziel erkennbar, die Vorschau scrollt am Rand mit. Spalten werden auf Desktop horizontal, bei mobiler Stapelung vertikal eingeordnet. Geöffnete Feinschliff-Gruppen bleiben auch bei schnellen Änderungen erhalten. Cachekennung 1.6.2; keine Schema- oder Datenbankmigration. Die Hilfen werden ausschließlich im Editor-DOM erzeugt und erscheinen weder in gespeicherten Dokumenten noch auf öffentlichen Seiten. Entscheidungen: ADR 075.

Referenz: Die tatsächlichen Builder-Dateien des bereitgestellten Flatsome 3.20.9 wurden erneut gelesen. Übernommen sind sichtbare Leerflächen, direkte Plus-Aktionen und schnelle Spaltenlayouts. Konkrete Fundstellen und Umfang stehen in docs/FLATSOME-ABGLEICH.md; keine laufende WordPress-Referenzinstallation geprüft.

Geprüft: npm.cmd test 30/30; PHP 8.4.22 vendor/bin/phpunit --filter EditorSecurityTest 27/27 mit 312 Assertions; JavaScript-Syntax und alle 19 Admin-Twig-Dateien gültig. Der vollständige isolierte npm.cmd run test:editor besteht einschließlich axe-Prüfungen. Neue Abdeckung: sichtbare leere Sektion, Tastaturwahl des Zweispaltenlayouts, Einfügen in genau die zweite Spalte, Ziehen in die erste Spalte mit Zielmarkierung und automatischem Scrollen, Undo/Redo, leere Layouts nach Speichern/Neuladen, Spaltenumsortierung Desktop/Mobil und Veröffentlichung ohne Editorhilfen. Desktop-, Mobil- und Drag-Ansichten anhand der Screenshots visuell geprüft. Bericht: reports/editor/result.json; Bilder: empty-section.png, empty-layout-desktop.png, empty-layout-mobile.png, empty-layout-drag.png im selben Ordner. Keine Änderungen an Kundendaten durch die isolierten Tests.

Bekannte Grenzen: Der öffentliche Renderer ist unverändert; Lighthouse wurde für diese Fassung nicht erneut gemessen. Die Messung zu 1.6.1 bleibt unten dokumentiert. Kein neuer Screenreader-, Firefox-, Safari- oder echter Touchgerätetest. Nächster Schritt: Die neuen Aufbauabläufe mit Umuts tatsächlichen Seiten nutzen und anschließend die restlichen Arbeitspakete des Entwicklungsplans fortsetzen.

1.6.1 – Editorbreite, neue Seitenabschnitte und passende Eigenschaften (14.09.2026)

Veröffentlicht am 14.09.2026: Commit b783d9d und annotierter Tag v1.6.1 sind auf origin (https://git.kant-it.de/kantarci/KantSites.git) hochgeladen. Das stabile Release 1.6.1 enthält ZIP, SHA-256-Datei und install.php. Das Archiv wurde aus dem exakten Commit mit Produktionsabhängigkeiten gebaut (3520 Dateien); Archivintegrität, Übereinstimmung mit dem Commit und Produktionscontainer wurden geprüft. Alle drei Anhänge anschließend ohne Anmeldung heruntergeladen und mit den lokalen Dateien verglichen; die API liefert 1.6.1 als neuestes stabiles Release. Archiv-SHA-256: a6e7da2bb14ede43dd2d94960b3b4d67cc285d9c717b560f796a91cbcde4dd26. Dieser Schritt veröffentlicht das Update, installiert es aber nicht auf Kundenwebsites.

Erledigt: Die festen 690/900/760-px-Grenzen für Text/H1/H2 sind entfernt. Elemente nutzen ihren Container, eigene Maximalbreiten bleiben möglich. Dauerhaft sichtbare Aktion Neue Sektion auf Seitenebene; auch der normale Sektionsklick erzeugt ohne expliziten Einfügepunkt einen übergeordneten Seitenabschnitt. Linke und rechte Sidebar sind per Ziehen und Tastatur verstellbar und merken ihre Breite im Browser. Die Vorschau nutzt ihre Fläche ohne Außenränder; die Elementbibliothek läuft nicht mehr horizontal über. Auf kleinen Fenstern passen sich die Panels an, auf dem Telefon stehen sie untereinander.

Feinschliff: Bild-, Text- und Containeroptionen werden passend gefiltert und sortiert. Gespeicherte Werte bleiben sichtbar; die gesamte Eigenschaftenauswahl ist über eine Schaltfläche erreichbar. Die Auswahl eines anderen Blocks setzt die Panelrolleiste nach oben. Geöffnete Eigenschaftengruppen bleiben beim Ändern eines Werts geöffnet. Automatische Größen und „Ohne Begrenzung“ sind explizit wählbar. Cachekennung auf 1.6.1 angehoben; keine Datenbankmigration, keine Kundeninhalte durch Tests geändert. Begründung und Rückweg: ADR 074.

Geprüft: npm.cmd test: 30/30; PHP 8.4.22 vendor/bin/phpunit: 452/452, 3476 Assertions. JavaScript-Syntax und alle 19 Admin-Twig-Dateien gültig; Produktionscache erfolgreich aufgewärmt, git diff --check sauber. npm.cmd run test:editor -- --audit vollständig bestanden: Seitenebenen/Undo/Redo, Panelbreiten per Maus/Tastatur und nach Neuladen, 390/850/1100/1920-px-Fenster, kontextbezogene Eigenschaften und bewusst gesetzte Textbreiten; auch Speichern, Neuladen, Veröffentlichen und Konfliktbehandlung. Sämtliche enthaltenen axe-Prüfungen ohne Verstoß. Desktop-, Bild- und Telefonansicht anhand der Browser-Screenshots visuell geprüft. Lighthouse 13.4.1, isolierte öffentliche Editortestseite im Produktionsmodus: alle fünf Kategorien 100, jeweils drei Läufe mobil und Desktop, auch jeder Einzellauf 100. Kein Nachweis für beliebige Kundeninhalte oder Feldwerte. Berichte: reports/editor/result.json, reports/lighthouse-editor/summary.json; Ansichten: reports/editor/flexible-workspace.png, context-image.png, editor-phone.png.

Bekannte Grenzen: Die geänderten Standardbreiten können den Zeilenumbruch bestehender Seiten verändern. Sektions- und ausdrücklich gespeicherte Elementbreiten bleiben wirksam. Kein neuer Screenreader- oder Felddatennachweis. Nächster Schritt: Umuts tatsächliche Editorabläufe mit realen Seiten durchgehen, danach den verbliebenen Ausbau gemäß Entwicklungsplan fortsetzen.

Vorheriger Stand und bisherige Arbeitspakete

Stand: 14.09.2026, Fassung 1.6.0. Das System heißt KantSites (vorher Arbeitstitel „Kant CMS“). Startseite von 18 auf 8 Datenbankabfragen, gemessenes Ladebudget, alle langen Listen blättern. Sicherung und Wiederherstellung, letztere nachgewiesen. Globale Seitenbreite wirkt jetzt auch nach oben, vier Menüplätze mit drei Menüarten, und jede Seite wählt ihren Kopf- und Fußbereich – auch „keiner“. 93 Gestaltungseigenschaften, neun Einblendungen, sieben Hover-Effekte, Symbol, Fortschritt und ein Slider ohne JavaScript. Update über Git mit Vorprüfung, Sicherung und Rückweg. Statistik ohne Wiedererkennung und damit ohne Einwilligung. Einwilligung mit tatsächlich zurückgehaltenen Einbettungen, Datenschutz und Impressum als Pflichtseiten. Benutzerverwaltung in der Oberfläche, Mediathek als eigene Seite mit Blättern. Die Verwaltung hat einen gemeinsamen Rahmen mit einer Navigation, die zeigt, wo man ist. Eigenes CSS je Element für alle, eigenes Skript nur für Administratoren und nur als eigene Datei. Formatierter Text mit Werkzeugleiste und HTML-Ansicht, serverseitig gegen eine Positivliste gefiltert. Kopf- und Fußbereich sind frei gestaltbare Blockdokumente, dazu verknüpfte wiederverwendbare Sektionen (Arbeitspaket G, Teil 1). Frei wählbare Startseite; /sitemap.xml und Geschwister waren lokal nie erreichbar und sind es jetzt. Seiten verschieben mit automatischen Weiterleitungen, Papierkorb, Fassungsverlauf und geplante Veröffentlichung (Arbeitspaket F). Browsertests wiederhergestellt und erweitert; dabei drei echte Fehler gefunden und behoben, darunter die in der Vorschau wirkungslose Gestaltung. Direktbearbeitung in der Vorschau, Kontrastwarnung, Gestaltungsvorlagen und Bildauswahl für Hintergrundbilder. Der Editor gestaltet frei statt über feste Auswahllisten: eigene Farbpalette, Schriften, und jede Eigenschaft je Block, je Gerät und für den Hover-Zustand frei einstellbar (Dokumentschema v10). SEO-Grundlage mit Sitemap, robots.txt, Weiterleitungen und strukturierten Daten (Arbeitspaket E). Formulare mit serverseitiger Validierung, Einwilligung, E-Mail-Versand und Spamschutz (Arbeitspaket D). Tabs als fortschreitende Verbesserung mit JavaScript (Dokumentschema v7). Zehn neue Blöcke für die Abschnitte der Pilotstartseite (Arbeitspaket C). Beiträge mit Datum, Autor, Auszug, Beitragsbild, Kategorien, Schlagwörtern, Archiven, einbettbarer Beitragsliste und RSS (Arbeitspaket B). Menüs mit Seiten-, Anker- und externen Zielen ersetzen die fest verdrahtete Navigation (Arbeitspaket A). Seiten und Beiträge lassen sich im Browser anlegen und löschen, mit Adresshierarchie bis vier Ebenen. Die vollständige Phase-1-Abnahme bleibt wegen des fehlenden UX-Builder-Referenzabgleichs offen.

Erster Commit angelegt (82b0782); das Projekt hat damit einen Rückfallpunkt. Noch kein Remote und kein Push.

Für den Livegang vorbereitet (13.09.2026): docs/LIVEGANG.md trennt, was geprüft ist, von dem, was nur auf dem echten Hosting geprüft werden kann, und nennt die Reihenfolge am Umzugstag. 14 Weiterleitungen von den alten WordPress-Adressen auf die neuen sind angelegt und beantwortet (/productsolutions → /projekte, die gekürzten Beitragsadressen, /category/allgemein); ohne sie hätte jede bei Google stehende Adresse nach dem Umzug mit 404 geantwortet. In der Datenschutzerklärung wurde der Abschnitt über Google Fonts an die Wirklichkeit angepasst (die Schriften kommen jetzt vom eigenen Server) und die Abschnitte über Google Maps und reCAPTCHA entfernt, weil beides nicht eingebunden ist – eine Erklärung, die Datenflüsse beschreibt, die es nicht gibt, ist so falsch wie eine, die welche verschweigt. Nicht geprüft und nicht prüfbar von hier: der E-Mail-Versand.

Verwaltung und Updateweg (1.4.3, 13.09.2026): Die Farbfelder im Designbereich haben einen Farbwähler samt Palettenauswahl; das Textfeld bleibt maßgeblich, damit @akzent ein Verweis bleibt und nicht beim ersten Klick zu einem festen Hexwert erstarrt (ADR 066). Die Verwaltung ist auf dem Telefon bedienbar: das Menü ist unter 760 px ein zugeklapptes <details>, das den aktuellen Bereich nennt, mit 44 px hohen Zielen – ohne Skript. Die zwölf Navigationssymbole sind eigene SVG-Pfade statt Unicode-Zeichen, die ein Telefon zu bunten Emoji macht (ADR 067). Und es gibt ein Update ohne Git: ArchiveUpdate lädt das Releasearchiv, vergleicht die Prüfsumme, sichert, ersetzt die Dateien und führt die Migrationen in einer zweiten Anfrage aus – der einzige Updateweg auf geteiltem Hosting (ADR 068, docs/UPDATE.md).

Nachbau nachgezogen (13.09.2026): Der Fußbereich ist jetzt vierspaltig wie im Original – Zeichen und Satz, „Leistungen", „Unternehmen", „Kontakt", darunter die Zeile mit Copyright. Die beiden Linklisten sind Menüs, keine Dokumentinhalte: sie lassen sich in der Verwaltung pflegen. Alle 29 Beiträge haben jetzt Text – die elf fehlenden sind neu geschrieben, denn auch im Original stehen dort nur Bildergalerien ohne Beschreibung. Die Karte „Bereit für den nächsten Schritt" hat 34 px Abstand zu den Kontaktzeilen darunter. Nicht erledigt: das TeamViewer-Logo im Fernwartungsbereich – dazu unten.

Vom Original übernommen (13.09.2026): die Meta-Beschreibung der Startseite im Wortlaut von kant-it.de, das Symbol der Website (dort 192 × 192) und die Bot-Regeln der robots.txt. Titel, Canonical, Open Graph, strukturierte Daten (LocalBusiness statt Yoasts ProfessionalService), Sitemap, robots.txt und llms.txt entstehen in KantSites aus dem Inhalt und stimmen mit dem Original überein. Beim Abgleich aufgefallen: das og:image des echten kant-it.de zeigt auf wp-content/uploads/kant-it-share.png – die Datei antwortet mit 404, beim Teilen erscheint dort also kein Bild.

Im Nachbau von kant-it.de gefunden und behoben: vier tote Verweise im Hero-Slider – die Adressen waren im Aufbauskript von Hand geschrieben, die Beiträge bekamen längere. Sie werden jetzt aus derselben Regel abgeleitet, mit der die Beiträge entstehen, und ein zweiter Lauf des Skripts aktualisiert vorhandene Seiten, statt sie zu überspringen. Eine Prüfung aller internen Verweise auf 404 gibt es im CMS noch nicht; gefunden wurden sie mit einem einmaligen Durchlauf im Browser.

Wiederkehrende Aufgaben ohne Cronjob (14.09.2026, ADR 073): Bis hierher stand in vier Dokumenten „gehört in einen Cronjob“ – auf geteiltem Hosting oft nicht einlösbar, und die Folge war nicht „läuft seltener“, sondern läuft nie. Jetzt gibt es einen Aufgabenplan mit vier Auslösern: jeder Seitenaufruf (nach der ausgelieferten Antwort, in kernel.terminate), das geöffnete Backend (im Minutentakt, nur solange der Reiter sichtbar ist), ein URL-Cronjob unter /cms-cron/<Schlüssel> für Hoster, die nur eine Adresse aufrufen können, und die Konsole (cms:tasks:run, auch als Dauerläufer mit --loop). Fünf Aufgaben sind angeschlossen: geplante Veröffentlichung (5 Minuten), Statistik verdichten, Sicherung, Updateprüfung und Aufräumen (je täglich). Der Bildschirm Aufgaben zeigt Plan, letztes Ergebnis, Dauer, Auslöser und Protokoll, schaltet jede Aufgabe einzeln und ändert ihren Abstand. Die Einschränkung steht auf diesem Bildschirm: ohne Besuch und ohne geöffnetes Backend passiert nichts – eine Aufgabe für 03:00 Uhr läuft auf einer stillen Website erst um 07:15 Uhr.

Erledigt

  • AGENTS.md und ENTWICKLUNGSPLAN.md unverändert aus Downloads im Projektstamm abgelegt; SHA-256 jeweils mit dem Original identisch.
  • Leeres lokales Git-Projekt auf main initialisiert; Quellen und Lockfiles für den ersten Commit vorgemerkt. Noch kein Commit und kein Remote/Push.
  • Nutzerentscheidung „herkömmliches PHP-Webhosting“ übernommen. Symfony 7.4 / Twig / Doctrine gewählt; kein Python-/Django-Anwendungscode.
  • Start-/Build-/Prüfkommandos und Hostingvoraussetzungen in README, begründete Architektur und Datenmodell in ARCHITECTURE, Plananalyse/offene Anforderungen in REQUIREMENTS.
  • Erstes Datenbankschema, versionierte Blockdokumente, unveränderliche Revisionen über den Repository-Vertrag, getrennte Entwürfe/Veröffentlichungen und optimistische Schreibkonflikterkennung.
  • Drei gespeicherte öffentliche HTML-Demoseiten, lokale CSS-Ausgabe ohne JavaScript, Metadaten/Canonical, 404, Sprunglink und sichtbarer Fokus. /home leitet auf / um.
  • Idempotentes Demo-Seeding und lokaler CLI-Import validierter Entwürfe. Keine ungeschützte HTTP-Schreibschnittstelle.
  • Symfony-Security-Anmeldung, CSRF, Login-Throttling, geschützte Sessions und Rollen Editor/Publisher/Admin. Neue Migration ergänzt ausschließlich die Benutzertabelle. Passwort-Rehash hält Sessionbenutzer und Datenbank synchron; Rollenänderungen invalidieren bestehende Sitzungen.
  • Seitenübersicht und visueller Editor: Elementbibliothek, Strukturbaum, gemeinsame serverseitige Live-Vorschau, Eigenschaften, Drag-and-drop und Schaltflächen zum Verschieben/Verschachteln, Duplizieren/Löschen, lokales Undo/Redo.
  • Inhalt-/Designbereiche mit typabhängigen Abständen, Ausrichtung, Schriftgröße/-stärke, Zeilenhöhe, Spaltenaufteilung/-abstand und Ecken. Gerätewerte Desktop → Tablet → Mobil samt Vererbung, Einzel-/Gerätereset und Undo. Echte Vorschau-Layoutbreiten 1240/768/390 px mit visueller Skalierung.
  • Drei bearbeitbare Abschnittsvorlagen (Intro, zwei Leistungen, Kontaktaufruf), einklappbarer Werkzeugkasten, Struktursuche mit Vorfahren, einklappbare Teilbäume, Pfadnavigation und dauerhaft erreichbare Blockaktionen.
  • Dokumentformat v2, erhaltender idempotenter v1-Konverter, gemeinsame Tokenliste für Validator/Editor/CSS und dokumentierter Migrations-/Rückweg. Bestehende v1-Seiten werden beim Öffnen nicht geändert; keine neue DB-Migration erforderlich.
  • Dokumentformat v3 für Galerie, Bildbeschriftungen/-varianten und Effekte, mit erhaltendem Upgrade ohne Downgrade und Undo. v1/v2 bleiben unterstützt. Schatten, Hover-Anheben/Bildzoom und Einblenden beim Laden ohne öffentliches JavaScript; Bewegungsreduktion wird berücksichtigt.
  • Medienverwaltung im Editor und unter /admin/media: JPEG/PNG/WebP-Upload, echte Bildvalidierung, Größen-/Pixel-/Speichergrenzen, WebP-Varianten bis 480/960/1600/2400 px (ohne Hochskalieren, Hintergrundbilder geräteabhängig), Suche, Titel/Alt-Text, Archivieren/Wiederherstellen und Schreibkonflikterkennung. Bildauswahl für einzelne Bilder und Galerien, kontextbezogene Alt-Texte/dekorative Verwendung und Beschriftungen.
  • Neue Medientabelle durch Migration 20260912000300 lokal angewendet. Vorher konsistentes SQLite-Backup unter var/backups/before-media-20260912-051258.db. Bestehende Seiten/Benutzer nicht geändert. Lokal läuft PHP 8.4 mit 8M Upload-, 10M POST- und 256M Speicherlimit; originale Uploads werden nicht gespeichert.
  • Speichern/Laden von Entwürfen, separates Veröffentlichen mit Bestätigung, sichtbare Speicher-/Fehler-/Konfliktzustände, JSON-Sicherung und bewusster Server-Neuladedialog. Keine Tests schreiben in den echten Seitenbestand.
  • Auf ausdrücklichen Nutzerwunsch lokalen Administrator umut mit zufälligem Passwort angelegt und im In-App-Browser erfolgreich angemeldet. Das Passwort steht in keiner Projektdatei; nur der Hash in der lokalen Datenbank. Keine Browser-Passwortspeicherung ausgelöst.
  • Seitenerstellung und -löschung im Browser: POST /admin/pages (ab ROLE_EDITOR) legt Seite oder Beitrag mit gültigem Startdokument als privaten Entwurf an; POST /admin/pages/{id}/delete (nur ROLE_ADMIN) entfernt eine Seite mit allen Revisionen. Beide mit CSRF-Token, Fehlermeldungen als Flash und ohne JavaScript bedienbar.
  • Adresshierarchie: neue Spalten path, parent_id, type, created_at über Migration 20260912000400; websiteweit eindeutiger Pfad statt eindeutigem Slug, Tiefe bis vier Ebenen, Beiträge ohne Unterseiten, reservierte Adressen und Adressableitung aus dem Titel. Öffentliche Route bedient verschachtelte Pfade wie /leistungen/webdesign; ein Lookahead hält /admin, /media und /assets aus dem Auffangmuster.
  • Ladeverhalten gemessen statt vermutet (ADR 039, Arbeitspaket M): ein Zähler liefert die Datenbankabfragen je Anfrage im Kopf X-Kant-Queries (außerhalb der Produktion). Die erste Messung zeigte 18 Abfragen auf der Startseite, weil Einstellungen und Theme je Aufrufer erneut gelesen wurden; beide merken sich ihren Stand jetzt für die Dauer einer Anfrage – 18 → 8. Die Browserprüfung misst je Seite HTML, CSS, Anfragen, Skripte und Abfragen gegen Obergrenzen: Startseite 7,4 KB HTML, 22,7 KB CSS, 2 Anfragen, 0 Skripte.
  • Aufgabenplan ohne Cronjob (ADR 073, Migration 20260914000100): eine Zeile je Aufgabe mit Fälligkeit, Sperre und letztem Ergebnis. Wer sie ausführen darf, entscheidet ein bedingtes UPDATE – auf MySQL wie auf SQLite atomar, damit zwei gleichzeitige Besucher eine Aufgabe nicht zweimal starten; die Sperre verfällt nach 15 Minuten von selbst. Der Normalfall kostet keine Datenbankabfrage: in var/scheduler.json steht, wann überhaupt das nächste Mal etwas ansteht. Ein Fehlschlag ist ein sichtbarer Zustand mit wachsendem Abstand bis zum nächsten Versuch (Verdopplung, gedeckelt bei sechs Stunden), und eine scheiternde Aufgabe reißt nie eine Seite mit. Neue Aufgaben sind eine Datei: wer App\Ops\Tasks\Task implementiert, steht beim nächsten Abgleich im Plan. Befehle: cms:tasks, cms:tasks:run (--task, --limit, --budget, --loop, --max-time).
  • Das erste Bild einer Seite lädt sofort, alles darunter weiterhin verzögert. Es ist meistens das größte Ding über der Falz.
  • Slider mit Punkten ohne Skript (ADR 044): ::scroll-marker zeichnet je Folie einen Punkt, der aktive ist erkennbar, die Rollleiste entfällt; ältere Browser behalten das wischbare Schiebefeld. Beitragskarten sind echte Karten (Bild 16:10, 18 px Ecken). Überschriften dürfen ein Wort hervorheben (<mark>, in Akzentfarbe). Behoben: die Theme-Schriftskala überschrieb die Größen in Karten und Aufruf-Bändern.
  • „Weiterlesen" ist jetzt ein Link. In der wechselseitigen Beitragsliste stand dort eine reine Beschriftung: sie sah aus wie ein Knopf und war keiner. Der Titel steckt für Vorlesesoftware im Linktext, damit nicht zwölf Links gleich heißen.
  • Behoben in 1.4.5: die Verwaltung zeigte Git-Fehler auf einer Installation ohne Git (ADR 072). git installiert und „Arbeitskopie" waren derselbe Schalter; auf Umuts Hosting ist Git vorhanden, die Installation kam aber aus dem Releasearchiv. Dort stand fatal: Not a git repository an der Stelle der Fassungsnummer und eine rote Vorprüfung für Bedingungen, die gar nicht gelten. Ohne Arbeitskopie verschwinden die Git-Karten jetzt.
  • Behoben in 1.4.5: „Sicherung anlegen" endete mit 500 (ADR 072). VACUUM INTO gibt es erst seit SQLite 3.27; Umuts Hosting hat eine ältere Fassung. Bei älteren wird unter einer Schreibsperre kopiert, und der Sicherungsknopf fängt jeden Fehler ab, statt mit einer 500-Seite zu enden.
  • System nennt die tatsächlich benutzte Datenbank und deren Quelle (1.7.0, ADR 076): Art, Dateipfad, Größe, Schreibrecht und .env beziehungsweise Server-Umgebung. Die bereits lokal begonnene Diagnose wurde in den neuen Konfigurationsvertrag übernommen. Verdeckte Datei-Overlays entfallen; die bisher wirksamen Werte werden einmalig gesichert und in .env zusammengeführt.
  • Das Update schreibt mit (1.4.6, ADR 071): Jeder Schritt des Archiv-Updaters geht sofort nach var/updates.log – während des Laufs, nicht am Ende. Auf Umuts Hosting war die Ergebnisseite tot, und damit war auch verloren, was der Updater berichtet hatte; genau so wurde der nächste Fehlschlag dann doch noch lesbar („bin ließ sich nicht beiseitelegen – der alte Stand wurde zurückgeschoben"). Die Ergebnisseite versteht zusätzlich die Schreibweise bis 1.4.4: Beim Update schreibt der alte Code die Schritte und der neue zeigt sie an.
  • Behoben in 1.4.5: der Updateknopf endete mit 500 (ADR 071). Auf Umuts Hosting gefunden, mit einer echten 1.4.3-Installation nachgestellt: Das Update lief vollständig durch – alle neun Schritte, Dateien getauscht, var/ und .env unberührt –, aber die Ergebnisseite starb daran. Der Updater über Git nennt einen Schritt step, der über das Archiv nannte ihn label, beide legen ihn unter demselben Sitzungsschlüssel ab, und die Vorlage liest step. Damit war auch der zweite Knopf unerreichbar und die Website blieb im Wartungszustand. Zehn Tests prüften das Rückgabe-Array, keiner die Seite, die es anzeigt; der neue Test rendert sie und wurde gegengeprüft, indem der Fehler noch einmal eingebaut wurde.
  • Migrationen im Browser ausführen (1.4.4, ADR 070): Wer ein Update von Hand über FTP einspielt, hat danach neuen Code und eine alte Datenbank. Der Bildschirm „System" zeigt jetzt eine Karte, sobald etwas aussteht, nennt die Namen und führt sie auf Knopfdruck aus – vorher ging das nur über eine Konsole, also genau das, was auf geteiltem Hosting fehlt. Der Weg von Hand steht Schritt für Schritt in docs/UPDATE.md.
  • Ein Fehlschlag beim Laden sagt, woran es lag (1.4.4, ADR 070): „Die Releaseseite antwortet nicht" war drei Ursachen in einem Satz. Beim Gegenprüfen des Releases fiel auf, dass diesem PHP der Zertifikatsspeicher fehlt – auf einem Webhosting der häufigste Grund und der einzige, den man in einer Minute behebt. Die Meldung nennt ihn jetzt, und bei genau diesem Fehler wird der zweite Weg über den Dateizeiger versucht. Abgeschaltet wird die Prüfung nicht.
  • Das Hintergrundbild des ersten Abschnitts wird vorab geholt (1.4.3, ADR 069): Auf der Startseite des Piloten ist das größte Element über der Falz kein <img>, sondern der Hintergrund des Heros – und der steht nur im Stylesheet, das der Browser erst nach dem HTML holt. Drei Vorabverweise mit denselben Gerätegrenzen wie der Compiler holen genau die Variante, die die Regel auch nimmt; ein imagesrcset hätte bei manchen Fensterbreiten ein zweites Bild geladen. Gemessen: LCP 1,3 s → 1,1 s, Leistung Desktop 96 → 97, Mobil 98 → 99 (auf dem Entwicklungsserver ohne Kompression). Dabei gefunden: dasselbe Bild wurde je Geräteregel neu abgefragt – die Mediathek merkt sich jetzt, wonach sie in dieser Anfrage schon gefragt wurde, Startseite 19 → 15 Abfragen.
  • Update aus dem Releasearchiv (1.4.3, ADR 068): der Weg für Installationen ohne Git – bisher stand dort nur „Dieses Verzeichnis ist keine Git-Arbeitskopie". Zwei Knöpfe im Bildschirm „System": der erste lädt, prüft die Prüfsumme, sichert und ersetzt die Dateien, der zweite führt die Migrationen aus – in einer eigenen Anfrage, weil ein Prozess, der sich selbst ausgetauscht hat, mit dem alten Code weiterarbeitet. var/, .env, hochgeladene Schriften und das erzeugte Stylesheet-Bündel bleiben unberührt, public/ wird dazu Datei für Datei zusammengeführt. Der alte Stand liegt danach unter var/update/. Geprüft mit acht Tests gegen eine Releaseseite im Speicher (tests/ArchiveUpdateTest.php), einer davon hält die Ersetzungsliste gegen die Mitnahmeliste des Releaseskripts.
  • Eigene Symbole in der Verwaltung (1.4.3, ADR 067): zwölf SVG-Pfade statt Unicode-Zeichen, die auf dem Telefon als Emoji in fremdem Stil erscheinen. Ein Test findet Emoji-Zeichen in der Navigationsvorlage und zwei Einträge mit demselben Pfad – beides war vorhanden.
  • Verwaltung auf dem Telefon (1.4.3, ADR 067): das Menü klappt unter 760 px zu und nennt in der Zusammenfassung den Bereich, in dem man steht; Ziele 44 px hoch, kein Skript, kein seitliches Scrollen. Geprüft bei 390 px.
  • Farbwähler im Designbereich (1.4.3, ADR 066): Farbfeld und Palettenauswahl neben dem Textfeld, das maßgeblich bleibt – ein <input type="color"> allein hätte @akzent in einen festen Wert verwandelt und die Verbindung zur Palette gekappt. Ohne JavaScript bleibt das Textfeld.
  • Behoben in 1.4.2: die Systemprüfung nennt jetzt die SQLite-Fassung. Auf dem Hosting scheiterte die Installation im dritten Schritt mit „near "(": syntax error" – dort läuft SQLite in einer Fassung vor 3.16, und Doctrine liest das Schema mit Tabellenfunktionen (pragma_table_info(...)), die es erst seit 3.16 gibt. Die Prüfung nennt die gefundene Fassung vor der Wahl der Datenbank, und der Datenbankschritt bietet SQLite gar nicht erst an, wenn sie zu alt ist – mit dem Hinweis auf MySQL. Eine Voraussetzung, die erst mitten in der Installation auffällt, ist keine Prüfung wert.
  • Behoben in 1.4.1: dem Releasearchiv fehlten migrations.php und cli-config.php. Auf dem Hosting brach die Installation deshalb im dritten Schritt ab – „The configuration file does not exist", genau dort, wo die Tabellen entstehen sollten. Die beiden Dateien standen weder in der Mitnahme- noch in der Ausschlussliste des Releaseskripts, also fiel niemandem auf, dass sie fehlen. Ein Test ordnet jetzt jeden Eintrag im Projektstamm einer der beiden Listen zu; was neu dazukommt und unbedacht bleibt, lässt ihn fehlschlagen. Der Installationsweg wurde anschließend vollständig durchgespielt: 3504 Dateien entpackt, 14 Migrationen ausgeführt, 15 Tabellen angelegt.
  • Fassung 1.4.0 ist veröffentlicht und der Installationsweg einmal vollständig durchgespielt: install.php aus dem Release geladen (anonym, ohne Anmeldung), in ein leeres Verzeichnis gelegt, aufgerufen – es lädt kantsites-1.4.0.zip (4,7 MB, 3502 Dateien mit vendor/), prüft die SHA-256-Summe, entpackt und löscht sich selbst. Die frische Installation antwortet mit der Systemprüfung unter /install. Das Repository ist dafür öffentlich – ein privates liefert die Downloadadresse ohne Anmeldung nicht aus.
  • Die Verwaltung kann umziehen (ADR 065): php bin/console cms:admin:path meine-verwaltung legt sie auf eine andere Adresse – geprüft wird, dass der Name frei ist und keine Seite verdeckt. Der Wert steht in .env.local und gilt für Routen, Firewall, Schutzkopfzeilen und Statistik. In der robots.txt wird ein geänderter Name nicht genannt; beim Standardnamen admin bleibt die Zeile, weil diese Adresse ohnehin bekannt ist. Die Systemseite zeigt die aktuelle Adresse an.
  • Dokumentschema v19: Baustein Rechtszeile (Impressum, Datenschutz, „Einwilligung ändern") für den Fußbereich – gestaltbar und an der richtigen Stelle statt als heller Streifen unter einem dunklen Fuß; die eingebaute Leiste erscheint nur noch, wenn die Seite den Baustein nicht hat. Dazu „Beim Klick" je Bild (nichts / vergrößern / Adresse) und „Bilder vergrößerbar" je Galerie.
  • Bildbetrachter (ADR 063): ohne JavaScript ein Verweis auf die große Bildfassung, mit JavaScript ein Betrachter über der Seite – <dialog> mit Blättern, Escape, Pfeiltasten und Zähler. Das Skript lädt nur auf Seiten, die ein vergrößerbares Bild enthalten.
  • Knopf „nach oben" (ADR 062), abschaltbar und vollständig einstellbar: Seite, Form, Größe, Farben, Schwelle, Beschriftung. Ein Anker ohne Skript; eingeblendet über eine Scroll-Zeitachse, wo der Browser sie kennt.
  • Behoben: zwei Stylesheet-Regeln griffen seit drei Veröffentlichungen nicht mehr (ADR 064). Beim Entfernen alter Regeln waren Selektorreste stehen geblieben; CSS nimmt dann die nächste Regel mit. Betroffen waren der Einwilligungsdialog und die Rechtszeile – beide standen ungestaltet unter dem Fuß. Ein Test prüft jetzt jede CSS-Datei auf abgebrochene Selektoren.
  • Menüs kosten nicht mehr je eine Abfrage. Jedes Menü holte die Seitenliste selbst; mit vier Menüs im Fuß waren das vier gleiche Abfragen je Seitenaufruf. Jetzt einmal je Anfrage gemerkt – Startseite des Nachbaus 21 → 18 Abfragen.
  • Behoben: die Vorschau zeigte beim Aufbau bis zu 1440 ms lang eine Seite ohne Gestaltung (ADR 061). Die Stile wurden erst angewandt, wenn das letzte Bild geladen war. Jetzt sitzen sie, sobald das Dokument geparst ist, und der Rahmen bleibt bis dahin verdeckt. Die Farben selbst stimmten immer – Vorschau und veröffentlichte Seite geben nachweislich dieselben berechneten Werte.
  • Cloudflare Turnstile für Formulare, optional (ADR 059): aus, bis in den Einstellungen beide Schlüssel stehen; dann erscheint die Prüfung in jedem Formular, und die Sicherheitsrichtlinie öffnet sich nur auf Seiten mit Formular für challenges.cloudflare.com. Antwortet Cloudflare nicht, wird die Anfrage trotzdem angenommen. Die eingebauten drei Prüfungen ohne Dritte bleiben unabhängig davon bestehen. Der Preis steht in der Verwaltung dabei: das Widget braucht JavaScript.
  • Behoben: die Fehlerseite eines Formulars hatte keine Gestaltung (ADR 060). Wer ein Pflichtfeld vergaß, bekam eine nackte Seite – der Formularcode baute sie selbst auf und ließ Theme, Blockstile und Schriften weg. Jetzt rendert derselbe Controller wie sonst auch.
  • Die Schriften liegen im Repository (public/assets/fonts/, mit lato-OFL.txt und LIZENZ.md daneben, wie die Open Font License es verlangt): auf einem frischen Server fehlt damit nichts, und es wird nie versehentlich von Google geladen. Andere Installationen holen ihre eigenen mit cms:fonts:fetch.
  • docs/NEUE-WEBSITE.md: die Anleitung, mit der eine neue Kundenwebsite entsteht – Reihenfolge, Dokumentformat, Bilder, SEO, Prüfläufe, und am Ende die fünf Fehler, die in diesem Projekt immer wieder passiert sind.
  • Schutzkopfzeilen ergänzt und geprüft: Permissions-Policy (Kamera, Mikrofon, Standort und Sensoren abgeschaltet) auf öffentlichen Seiten und in der Verwaltung; X-Powered-By wird entfernt – der Server nannte bisher in jeder Antwort seine genaue PHP-Fassung. Die Browserprüfung misst die Kopfzeilen jetzt, statt sie anzunehmen.
  • Behoben: der Editor kannte Schema v17, der Validator v18. In einem aktuellen Dokument war die Beitragsnavigation nicht einfügbar. Ein Test liest die höchste Fassung jetzt aus dem Validator und vergleicht sie mit der des Editors – dieselbe Falle war schon bei v13 zugeschnappt.
  • Getrennter Titel für Suchergebnisse (Migration 20260913000100): jede Seite und jeder Beitrag kann einen eigenen <title> tragen, unabhängig von der Überschrift auf der Seite. Leer heißt weiterhin: der Titel der Seite gilt. Er zählt auch für og:title.
  • Schriften kommen vom eigenen Server (ADR 057): ein Knopf im Designbereich oder php bin/console cms:fonts:fetch lädt die gewählten Google-Schnitte als WOFF2 nach public/assets/fonts und schreibt die @font-face-Regeln daneben. Damit lädt keine Seite mehr etwas von Google – das spart zwei fremde Verbindungen nacheinander (gemessen 820 ms) und die datenschutzrechtliche Angreifbarkeit gleich mit. Ohne Dateien bleibt es beim bisherigen Verweis.
  • Ein Stylesheet statt vier, und eine .htaccess, die etwas tut (ADR 056): gebündelt, ohne Kommentare, mit Prüfsumme im Dateinamen; Kompression, ein Jahr Cache für Dateien mit Fassung oder Kennung in der Adresse, Schutz vor dem Ausliefern interner Dateien, HTTPS-Umleitung und HSTS vorbereitet und auskommentiert. Der Entwicklungsserver sendet dieselben Kopfzeilen, damit lokale Messungen etwas bedeuten.
  • Bilder in der Größe, in der sie erscheinen (ADR 058): ein Hintergrundbild, das nur für „Desktop“ gesetzt wurde, gilt auf dem Telefon in der Telefongröße (gemessen 189 KB → 40 KB); <img> nennt seine feste Breite, wenn es eine hat, und sonst sizes="auto"; fetchpriority="high" liegt auf dem ersten Bild des Inhalts statt auf dem Logo im Kopfbereich.
  • Lighthouse gegen den Nachbau von kant-it.de (docs/quality/lighthouse-kantit.json, vier Adressen, je drei Läufe, Median): Barrierefreiheit, Best Practices, SEO und „agentic browsing“ 100 auf allen geprüften Seiten, mobil wie am Schreibtisch. Performance: Projekte-, Beitrags- und Impressumsseite 100/100, Startseite 98 mobil / 96 am Schreibtisch – dort liegen vier Sliderbilder und ein großflächiges Hintergrundbild über der Falz. Gemessen im Produktionsmodus mit denselben Kopfzeilen, die public/.htaccess setzt; keine Felddaten und keine Messung auf echtem Hosting.
  • Verweise werden geprüft (npm run test:links): folgt der Sitemap, sammelt jeden internen Verweis samt Bildern, Stylesheets und Hintergrundbildern und meldet jede Antwort ab 400. Im Nachbau 408 Adressen, kein toter Verweis.
  • Das Vorschaubild beim Teilen erscheint jetzt tatsächlich (ADR 056): og:image wird aus dem Feld der Seite aufgelöst, ersatzweise aus dem Beitragsbild – vorher speicherte das Feld und bewirkte nichts, keine KantSites-Seite hatte ein Teilen-Bild. Dazu og:site_name, og:locale, og:image:alt, twitter:card (klein ohne Bild, groß mit) und das Bild im strukturierten Datensatz eines Beitrags. Archive haben jetzt ebenfalls Open-Graph-Angaben.
  • Eigene robots.txt-Zeilen: 200 statt 30, Leerzeilen bleiben (ADR 056), damit sich Regeln je Bot in Blöcken schreiben lassen – etwa welche KI-Crawler lesen dürfen. Ein Sitemap-Index ist bewusst nicht gebaut: nötig wird er ab 50.000 Adressen.
  • Beiträge sind optional (ADR 053): ohne einen einzigen veröffentlichten Beitrag antworten /beitraege und /feed.xml mit 404, der Feedverweis fehlt im Kopf, die Sitemap kennt kein Archiv, und ein Beitragslisten-Baustein zeigt öffentlich nichts – der Hinweis „hier erscheinen Beiträge“ bleibt nur im Editor. Ein Kategorie- oder Schlagwortarchiv verschwindet ebenso, sobald es nichts mehr zu zeigen hat; der Begriff selbst bleibt. Dabei behoben: die Beitragsliste fragte je Beitrag einzeln nach Bild und Begriffen – jetzt je eine Abfrage für die ganze Liste, Startseite des Nachbaus 22 → 16 Abfragen.
  • Unterstriche nur noch im Fließtext (ADR 054), dort mit Schalter im Designbereich („Links im Fließtext unterstreichen“, Standard an). Karten, Menü, Kontaktzeilen, Rechtszeile und der „Ansehen“-Streifen über Kartenbildern haben keinen mehr; beim Überfahren erscheint er überall. Die aktuelle Seite im Menü wird über das Schriftgewicht kenntlich – die Akzentfarbe fiel im dunklen Fußmenü durch die Kontrastprüfung.
  • Eigenes Symbol der Website (ADR 055): ein Bild aus der Mediathek in den Einstellungen wählen, beim Speichern entstehen daraus quadratische PNG-Dateien in 32 und 180 px (/favicon.png, /apple-touch-icon.png). Ohne Auswahl bleibt es beim mitgelieferten Zeichen.
  • Die Browserprüfung prüft jetzt jede Installation, nicht nur die Demo: die geprüften Seiten kommen aus der Sitemap, Tastatur- und Ohne-JavaScript-Weg folgen dem ersten Verweis im Inhalt statt festen Linktexten der Demo. Das Ladebudget trennt Bilder (Inhalt) vom Beiwerk (≤ 12 Anfragen) und deckelt stattdessen das übertragene Gewicht (1,5 MB); Skripte werden gegen die Richtlinie der Seite gezählt, strukturierte Daten zählen nicht mit. Der Nachbau von kant-it.de besteht die vollständige Prüfung ohne Befund.
  • Einblendungen laufen beim Scrollen (ADR 052): animation-timeline: view() hinter Bewegungsschranke und @supports; auf dem Scrollweg nur Bewegung, keine Deckkraft, damit Text außerhalb des Blicks lesbar bleibt. Die drei Beitragsvorlagen bringen sie mit.
  • Vier Darstellungen der Beitragsliste (ADR 051): Karten, Liste, Kompakt und Wechselseitig – am Baustein einstellbar, dieselben Daten, nur andere Regeln.
  • Drei Aufbauten für neue Beiträge (ADR 050): „Bildkopf“, „Klassisch“ und „Magazin“, wählbar beim Anlegen. Startpunkte, keine Schablonen – jeder Block bleibt änderbar.
  • Dokumentschema v18 (ADR 049): Baustein „Beitragsnavigation“ – Vor/Zurück zwischen Beiträgen derselben Kategorie, vom Renderer gefüllt, ohne Umbruch am Rand. Dazu Autorotation für den Slider (Sekunden, eigenes Skript nur auf Seiten mit einem solchen Slider) und Blättern in der Beitragsliste.
  • Dokumentschema v17 (ADR 043): eine Beitragsliste kann Datum, Auszug und Kategorie ausblenden und damit als Projektraster erscheinen; ein Bild kann verlinken (Logo auf die Startseite). Dazu die Einstellung „Kopfbereich beim Scrollen oben halten“. Behoben: im Editor ließ sich in Dokumenten ab v13 kein Baustein mehr einfügen.
  • Die Gestaltung färbt jetzt auch die Bausteine (ADR 042): Symbole, Knöpfe, Rahmen, ruhiger Text, Fokusring und Einwilligungsdialog nehmen die Farben des Themes statt fester Werte aus site.css. Der Einwilligungsdialog lässt sich zusätzlich eigens färben über eine Palettenfarbe mit dem Schlüssel consent. Überschriften werden nicht mehr getrennt.
  • Installer (Arbeitspaket N, ADR 041): /install führt in sechs Schritten von einer leeren Installation zu einer Website – Systemprüfung, Datenbank (SQLite oder MySQL, Verbindung sofort geprüft), Migration im Prozess ohne Konsole, Website mit Startseite, Kopf-/Fußbereich, Menü sowie Impressum und Datenschutzgerüst, erstes Administratorkonto, Abschlussliste. Solange keine Installation existiert, führt jede Adresse dorthin; sobald ein Konto existiert, antwortet /install mit 404. Davor liegt scripts/install.php: eine einzelne Datei, die das Releasearchiv mit fester Adresse und fester SHA-256 lädt, gegen ZIP-Slip gesichert entpackt und sich danach selbst löscht.
  • Fassungsverlauf, Einsendungen und Weiterleitungen blättern statt bei 30, 50 beziehungsweise 2000 Einträgen stillschweigend aufzuhören.
  • Sicherung und Wiederherstellung (ADR 038, Arbeitspaket O): cms:backup schreibt Datenbank (über VACUUM INTO, nicht als Dateikopie) und Medienordner nach var/backups/, mit Manifest samt Prüfsumme, und rotiert die alten. In der Verwaltung gibt es die Übersicht mit Zustand je Sicherung und einen Knopf zum Anlegen. Zurückspielen nur auf der Konsole (cms:restore): Prüfsumme, Fassungsvergleich, Sicherheitskopie des aktuellen Stands, Wartungszustand, Datenbank ersetzen, Medien ergänzen statt ersetzen. Nicht enthalten: .env, vendor/, Quellcode – steht so im Manifest.
  • Seiten ohne Menü, andere Köpfe je Seite (ADR 037, Dokumentschema v16): jede Seite wählt Kopf- und Fußbereich – Standard, ein anderer Bereich oder keiner, für eine Landingpage ohne Navigation. Dafür sind mehrere Kopf- und Fußbereiche erlaubt, jeder mit eigener Adresse; der erste bleibt default. Ein Verweis auf einen gelöschten Bereich fällt auf den Standard zurück statt die Navigation stillschweigend zu verlieren.
  • Vier Menüplätze statt zwei (Hauptnavigation, zweite Navigation, Fußzeile, Rechtliches) und drei Menüarten: waagerecht, untereinander, aufklappbar als Burger. Der Burger ist ein details-Element und öffnet ohne JavaScript.
  • Die globale Seitenbreite wirkt jetzt auch nach oben. containerWidth im Designbereich gab es, aber site.css deckelte bei 1180 px – schmaler ging, breiter nicht. Dritter Fehler dieser Art an einem Tag: eine Einstellung, die sich speichern ließ und nichts tat.
  • Vollflächige Bilder und volle Bildschirmhöhe: Abschnitte können randlos sein (ohne Begrenzung, ohne Rand, von Kante zu Kante) oder breit (1600 px). Für die Höhe gibt es jetzt die Einheiten dvh, svh, lvh – 100vh ist auf dem Handy höher als der Bildschirm, 100dvh ist das, was gemeint ist. Die Vorlage „Vollbild-Hero“ setzt die ganze Kombination auf einen Klick: randlos, 100dvh (mobil 80dvh), Inhalt senkrecht zentriert, Hintergrundbild formatfüllend, dunkle Überlagerung, Überschrift, Text, Button.
  • Designer ausgebaut (ADR 036, Dokumentschema v15): 93 Gestaltungseigenschaften statt 50, je Gerät und teils für Hover. Neu: Bild-Eigenschaften, die auf das Bild im Block wirken (Ausschnitt, Bildmitte, Seitenverhältnis, Höhe, Ecken, Rahmen), Positionierung mit z-index, getrennte Zeilen- und Spaltenabstände, Flex-Eigenschaften, Innen- und Textschatten, acht Bildfilter, Mischmodus, Zusatzrahmen. Neun Einblendungen mit Verzögerung, sieben Hover-Effekte, alle innerhalb von prefers-reduced-motion. Drei neue Bausteine: Symbol, Fortschritt (mit role="meter") und Slider als Scrollbereich mit Einrasten – ohne JavaScript, ohne Autoplay. „Zeile, 2/3/4 Spalten“ legt eine Zeile samt Spalten auf einen Klick an. Der Vergleich mit Flatsome steht in docs/DESIGNER-VERGLEICH.md, mit dem was fehlt und warum.
  • Zwei Fehler dabei gefunden und behoben: Rahmenstärken je Seite erzeugten border-width-top – kein gültiges CSS, vom Browser stillschweigend verworfen, also ohne jede Wirkung. Und eine Rundung auf einem Bildblock rundete den Rahmen, nicht das Bild.
  • Update über Git (ADR 035, Arbeitspaket N/UPDATE.md): cms:update und ein Bildschirm „System“ zeigen Fassung, Commit, verfügbaren Tag und eine Vorprüfung, die jede Bedingung einzeln nennt – unter anderem, ob Datenbank und Medienordner von Git ausgenommen sind. Der Ablauf: sichern, Wartungszustand (503 mit Retry-After, /admin bleibt offen), Checkout des Tags, Fassung prüfen, Composer, Migration, Cache, Rauchtest als eigener Prozess (cms:health). Scheitert ein Schritt, wird auf den vorherigen Commit zurückgesetzt, die Datenbank nur dann zurückgeschrieben, wenn die Migration schon begonnen hatte – und wenn der alte Stand antwortet, geht die Website wieder online. --check, --dry-run, --to=v1.2.0 und --maintenance=an|aus gibt es auf der Konsole.
  • Statistik (ADR 034, Arbeitspaket L): Aufrufe je Tag und Stunde, meistaufgerufene Seiten, Verweise von außen und nicht gefundene Adressen, als CSV exportierbar. Gespeichert wird ein Zähler je (Tag, Stunde, Pfad, Status, Verweis-Domain), kein Datensatz je Aufruf – keine IP-Adresse, keine Kennung, kein Cookie, auch nicht gehasht. Deshalb braucht diese Messung keine Einwilligung. Diagramme zeichnet der Server als SVG, ohne Skript. Was sie nicht kann, steht auf dem Bildschirm: Aufrufe statt Besucher, keine Verweildauer, keine Absprungrate, kein Klickweg, kein Land. Nach 60 Tagen werden Stunden zu Tageswerten verschmolzen, nach 425 Tagen gelöscht (cms:stats:prune).
  • Einwilligung (ADR 032, Dokumentschema v14): serverseitig gerendertes Banner ohne JavaScript, „Alle akzeptieren“ und „Nur notwendige“ gleich groß nebeneinander, Auswahl je Gruppe im Aufklappfeld. Die Entscheidung steht in einem eigenen Cookie – keine Kennung, kein Serverabgleich, ein halbes Jahr. Unter /einwilligung jederzeit änderbar, von jeder Seite verlinkt. Der neue Block „Externer Inhalt“ lädt ohne Einwilligung nichts: kein iframe, keine Adresse im Markup, frame-src 'none'. Der Platzhalter nennt den Anbieter und gewährt auf Knopfdruck genau diese Gruppe. Ist der Hinweis abgeschaltet, laden die Inhalte – sonst würde das Abschalten jede Seite stillschweigend leeren.
  • Datenschutz und Impressum sind Pflicht, nicht Einstellung (ADR 033): fehlen sie, warnt die Verwaltung und legt sie auf Knopfdruck als Gerüst mit NOCH AUSZUFÜLLEN-Markierungen an. Verlinkt werden sie aus dem Template, nicht aus dem Fußbereich, damit der Link nicht versehentlich gelöscht werden kann.
  • Kopf- und Fußbereich lagen falsch im Layout. Umut hat es im Screenshot gesehen: die Wortmarke stand als Buchstabenkolonne in einem 60 Pixel breiten Kasten. Zwei Ursachen, beide behoben – .site-header war noch ein Flexcontainer für das alte, handgeschriebene Markup und quetschte die Sektion darin zusammen, und design.css band alle Designklassen an #main, sodass Kopf- und Fußbereich außerhalb davon jede Designeinstellung ignorierten.
  • Benutzerverwaltung (ADR 030): Konten anlegen, Rolle ändern, Passwort setzen, Konto löschen – nur für Administratoren. Eine Rolle je Konto (Administrator, Veröffentlicher, Redakteur). Ein Passwort oder eine Rolle zu ändern meldet jede offene Sitzung dieses Kontos ab, weil die Sitzung Hash und Rollen gegen die Datenbank prüft; nur die Sitzung, die ihr eigenes Passwort ändert, bleibt angemeldet. Niemand kann sein eigenes Konto löschen oder herabstufen, deshalb bleibt immer ein Administrator übrig. Das eigene Passwort darf jeder ändern, aber nur mit dem aktuellen. Die Liste zeigt letzte Anmeldung, Passwortdatum und Anlagedatum.
  • Mediathek als Seite (ADR 031): /admin/media zeigt die Bilder direkt, 24 je Seite, mit Blättern und Archivfilter. Hochladen und Bildangaben über gewöhnliche Formulare, ohne JavaScript. Hochgeladen wird über eine Ablagefläche: Bilder hineinziehen oder klicken; darunter steht, welche Dateien gewählt sind und welche übersprungen wurden. Darunter liegt ein echtes, per Tastatur erreichbares Dateifeld – ohne JavaScript bleibt es der Dateidialog, und abgeschickt wird immer erst auf Knopfdruck. Der Dialog bleibt für die Bildauswahl im Editor.
  • Verwaltung neu gestaltet (ADR 029): ein Rahmen für alle Bildschirme mit fester Seitenleiste, aktivem Eintrag über aria-current, Titel, einem Satz Erklärung und Platz für die Hauptaktion. Vorher trug jede Seite ihre eigene Kopfzeile und eine eigene, unvollständige Linkliste. Je Seite eine Zeile mit dem Alltäglichen und einem Aufklappfeld „Einstellungen“, das an seinem Platz öffnet, statt sechs Kästen, die über den Rand hinausragten. Tabellen scrollen seitlich, statt die Karte aus dem Fenster zu schieben. Die Editorregeln stehen jetzt in editor.css, das nur Editor und Mediathek laden.
  • Verwaltung neu gestaltet (ADR 029): ein Rahmen für alle Bildschirme mit fester Seitenleiste, aktivem Eintrag über aria-current, Titel, einem Satz Erklärung und Platz für die Hauptaktion. Vorher trug jede Seite ihre eigene Kopfzeile und eine eigene, unvollständige Linkliste. Je Seite eine Zeile mit dem Alltäglichen und einem Aufklappfeld „Einstellungen“, das an seinem Platz öffnet, statt sechs Kästen, die über den Rand hinausragten. Tabellen scrollen seitlich. Der Editor behält seinen Aufbau und übernimmt die neue Gestaltung; seine Regeln stehen jetzt in editor.css, das nur Editor und Mediathek laden.
  • Eigenes CSS je Element (Dokumentschema v13) im neuen Reiter „Erweitert“: Eigenschaften für das Element und verschachtelte Regeln mit &. Der Selektor wird vom Server gesetzt, die Ausgabe neu serialisiert – eine überzählige } kann den Block nicht verlassen. Abgewiesen: @-Regeln, Backslash-Escapes, expression(), fremde Selektoren und jede Adresse außer einem einzelnen / oder https://. Für jeden mit Editorrolle.
  • Eigenes Skript je Element nur für Administratoren. Durchgesetzt beim Schreiben durch Vergleich mit dem gespeicherten Stand – ein Editor kann eine Seite mit Skript weiter bearbeiten, das Skript aber nicht anfassen, auch nicht über eine eigene Anfrage. Ausgeliefert als /assets/seite/{id}.js nur für veröffentlichte Seiten, jedes Skript in einer eigenen Closure mit element und try/catch. Nur eine Seite mit eigenem Skript sendet script-src 'self', jede andere behält script-src 'none'; inline ist nirgends erlaubt. In der Editorvorschau wird nichts ausgeführt.
  • Formatierter Text in Textblöcken (Dokumentschema v12): fett, kursiv, unterstrichen, durchgestrichen, Code, hervorgehoben, hoch-/tiefgestellt, Absätze, Listen und Links. Im Editor eine Werkzeugleiste plus HTML-Ansicht; eingefügter Inhalt kommt als reiner Text an. Gefiltert wird auf dem Server beim Speichern, nicht im Editor und nicht beim Rendern – was in der Datenbank liegt, ist schon sicher. Nicht erlaubte Elemente werden ausgepackt, der Text bleibt; script, style, iframe und Verwandte werden samt Inhalt entfernt. Attribute fallen weg außer href nach Adressprüfung. Ältere Dokumente behalten Klartextsemantik und werden weiter maskiert.
  • Kopf- und Fußbereich sind Blockdokumente unter /admin/partials, im selben Editor gestaltbar wie eine Seite. Im Template steht keine Marke und keine Navigation mehr; ein neuer Block menu zeigt einen Menüplatz. Geteilte Bereiche haben keinen Entwurf und keine Veröffentlichung – gespeichert heißt live – und dürfen keine Hauptüberschrift enthalten, weil jede Seite ihre eigene bringt.
  • Wiederverwendbare Sektionen werden verknüpft, nicht kopiert (Block partialref, Dokumentschema v11): die Seite speichert nur die Adresse, eine Änderung erreicht jede Seite, die sie einbindet. Zyklen werden abgewiesen, Verschachtelung auf drei Ebenen begrenzt, eine entfernte Sektion hinterlässt keine Lücke und der Verweis bleibt gespeichert. Die Abschnittsvorlagen kopieren weiterhin – beide Wege stehen bewusst nebeneinander.
  • Frei wählbare Startseite unter Einstellungen. Es gibt jetzt eine Quelle dafür – SiteSettings::homePath() und publicPath(); das Literal home kommt im Anwendungscode nicht mehr vor. Die gewählte Seite muss veröffentlicht und nicht im Papierkorb sein, ihre eigene Adresse leitet dauerhaft auf /, und solange sie die Wurzel ist, lässt sie sich nicht umbenennen oder löschen.
  • /sitemap.xml, /robots.txt, /llms.txt und /feed.xml waren lokal nie erreichbar, obwohl jeder Kernel-Test sie bestand: der eingebaute PHP-Server antwortet für fehlende Pfade mit Dateiendung selbst mit 404. bin/dev-router.php bildet die Rewrite-Regel des Hostings nach; Start- und CI-Befehl nutzen es, und der Browsertest ruft die vier Adressen jetzt über HTTP auf.
  • Barrierefreiheitsbefunde werden nach Ursache getrennt: ein Kontrastfehler aus Theme oder Stylesheet bricht den Lauf ab, einer aus einer im Editor gesetzten Blockfarbe wird bei jedem Lauf mit Seite, Farbe und Verhältnis ausgegeben und in reports/browser.json geschrieben. Stillschweigend durchgelassen wird nichts.
  • Seiten umbenennen und verschieben zieht den ganzen Teilbaum mit und hinterlässt für jede alte Adresse eine dauerhafte Weiterleitung. Kollisionen, Tiefengrenze und das Verschieben unter eigene Unterseiten werden vorher abgewiesen; alles in einer Transaktion.
  • Papierkorb statt sofortigem Löschen für die Editorrolle: alle Fassungen bleiben, öffentlich 404, verschwunden aus Navigation, Archiven, Feed, Sitemap, llms.txt und Editorliste. Unterseiten gehen mit; beim Wiederherstellen muss das Elternteil zuerst zurück. Endgültiges Löschen bleibt der Administratorrolle.
  • Fassungsverlauf je Seite unter /admin/pages/{id}/revisions mit Wiederherstellen. Das überschreibt nichts: die alte Fassung wird als neuer Entwurf gespeichert.
  • revision.seq behebt einen echten Fehler: die Reihenfolge des Verlaufs war nicht eindeutig, weil created_at nur Sekunden kennt und mehrere Speichervorgänge in dieselbe Sekunde fallen. Jede Fassung hat jetzt einen je Seite aufsteigenden Zähler.
  • Geplante Veröffentlichung über publish_at und den Cron-Befehl cms:publish-due. Ohne Cron bleibt die Seite ein Entwurf. Zeiten in UTC; Seiten im Papierkorb werden nie durch den Zeitplan veröffentlicht.
  • Die erzeugten Stile wirkten in der Editorvorschau nicht. Ein srcdoc-Iframe erbt die Richtlinie der Verwaltung, die Inline-Styles verbietet; das Stylesheet wurde stillschweigend verworfen. Die Vorschau liefert es jetzt als inertes <template>, der Editor wendet es über adoptedStyleSheets an. Ausgeliefert wurde es die ganze Zeit korrekt – nur die Vorschau log.
  • Die Einrückung der Unterseiten in der Seitenliste hat nie funktioniert: sie lief über ein style-Attribut, das dieselbe Richtlinie verwirft. Jetzt über Klassen.
  • Das Theme überschrieb alle Linkfarben und erzeugte damit 3,08:1 auf dunklem Grund. Die Regel gilt jetzt nur für Links in Textblöcken; die voreingestellte Linkfarbe #a4510f liegt auf allen hellen Tönen über 4,5:1.
  • Bewegungseffekte (Schatten, Hover, Einblendung) bleiben als eigener Panelabschnitt für jeden Block: sie sind Keyframes und lassen sich mit freien Deklarationen nicht ausdrücken. Die alten Tokenwerte erscheinen nur noch, wo eine Seite sie trägt.
  • Direktbearbeitung in der Vorschau: Doppelklick auf Überschrift, Text, Zitat, Aufruf-Band, Symbolkarte, Akkordeon-Eintrag, Tab-Titel oder Kennzahl bearbeitet den Text an Ort und Stelle; Escape beendet, Eingaben werden zu einem Undo-Schritt zusammengefasst. Die Vorschau führt dabei weiterhin kein eigenes JavaScript aus – der Editor greift in ihr Dokument. Eine Schaltfläche im Panel startet dasselbe ohne Maus.
  • Kontrastwarnung im Designpanel: Verhältnis zwischen wirksamer Text- und Hintergrundfarbe nach WCAG 2.1 mit drei Stufen. Eine Warnung, kein Verbot.
  • Gestaltungsvorlagen im Theme: eine Gestaltung unter einem Namen speichern und auf jedem Block jeder Seite anwenden, dazu Kopieren und Einfügen zwischen Blöcken innerhalb der Sitzung. Vorlagen werden mit demselben Compiler geprüft wie Blockstile.
  • Hintergrundbilder werden über die Mediathek gewählt statt über eine eingetippte ID.
  • Freie Gestaltung (Dokumentschema v10). StyleSchema erklärt jede Eigenschaft einmal mit Typ, CSS-Entsprechung und Grenzen; Validator, CSS-Erzeugung und Editorpanel lesen dieselbe Definition. Rund fünfzig Eigenschaften in sieben Gruppen: Typografie (Schriftart, Größe, Stärke, Lage, Zeilenhöhe, Buchstaben- und Wortabstand, Ausrichtung, Schreibweise, Textlinie, Farbe), Hintergrund (Farbe, Verlauf, Bild mit Größe/Ausschnitt/Wiederholung/Parallax, Überlagerung), Abstände je Seite, Größen, Rahmen je Seite und Ecken je Ecke, Layout (Flex, Raster, Verteilung, Abstand, Reihenfolge) und Wirkung (Deckkraft, freier Schatten, Verschieben, Skalieren, Drehen, Übergangsdauer). Werte je Gerät mit Vererbung Desktop → Tablet → Mobil und einzeln zurücksetzbar; farbige, numerische und Bewegungswerte zusätzlich für den Hover-Zustand.
  • Theme unter /admin/theme: frei definierbare Farbpalette (bis 40 eigene Farben, im Editor als @schlüssel), Schriften aus Systemschrift oder Google Fonts mit Auswahl der Schnitte, und die Grundtypografie aus Schrift, Grundgröße, Zeilenhöhe, Überschriftenskala, Inhaltsbreite sowie Text-, Hintergrund- und Linkfarbe. Eine Palettenfarbe zu ändern wirkt überall, wo sie verwendet wird.
  • Der Server erzeugt pro Seite ein blockgenaues Stylesheet mit Media Queries und :hover,:focus-within. Es steht inline und ist über seinen eigenen SHA-256-Hash erlaubt, nicht über unsafe-inline. Google Fonts auf ausdrückliche Nutzerentscheidung – das bedeutet eine Anfrage an Google je Seitenaufruf; die Richtlinie öffnet die Hosts nur, wenn tatsächlich eine Google-Schrift eingestellt ist.
  • SEO: Indexierungssteuerung und Vorschaubild je Seite, Unternehmensdaten unter /admin/settings, sitemap.xml, robots.txt, Weiterleitungen mit 301 und 410, optionale llms.txt, Brotkrumen-Block (Dokumentschema v9) sowie strukturierte Daten für Organization/LocalBusiness, Article und BreadcrumbList. Alles Maschinenlesbare wird aus dem veröffentlichten Bestand abgeleitet: eine nicht indexierbare Seite verschwindet gleichzeitig aus der Sitemap und bekommt ein noindex-Meta. Ohne gepflegte Unternehmensdaten wird kein leerer Organization-Block ausgegeben. llms.txt ist standardmäßig aus.
  • Dokumentschema v8: Formulare. Verwaltung unter /admin/forms mit Feldtypen Text, E-Mail, Telefon, Mehrzeiliges, Auswahl und Einwilligung, Empfänger, Betreff, Antwortadresse und Erfolgsmeldung. Der Formularblock im Seiteninhalt enthält nur die Adresse – Felder und Empfänger liegen in der Datenbank, damit gespeicherter Inhalt nicht ändern kann, wohin eine Anfrage geht. Serverseitige Validierung mit einer Meldung je Feld über aria-describedby/aria-invalid, Eingaben bleiben bei Ablehnung erhalten (HTTP 422), gültige Absendung per HTTP 303 zurück auf die Seite. Spamschutz ohne Dienst Dritter: Honeypot, signierter Zeitstempel und fünf Absendungen je Absender und Stunde. Die Absenderadresse wird nie gespeichert, nur ein Hash; der Zeitpunkt der Einwilligung getrennt. Gespeichert wird vor dem Versand, damit ein kaputter Mailversand keine Anfrage verliert; nicht versendete Einsendungen sind in der Verwaltung gekennzeichnet. Voreingestellter Transport sendmail, per MAILER_DSN auf SMTP umstellbar. Kein CSRF-Token auf dem öffentlichen Weg, weil dafür eine Sitzung und damit ein Cookie auf jeder öffentlichen Seite nötig wäre; Begründung in ADR 017.
  • Dokumentschema v7: Tabs. Der Server liefert Ankerliste und alle Panels mit Überschriften, also vollständigen Inhalt ohne JavaScript; public/assets/tabs.js macht daraus ein echtes Tab-Widget mit tablist/tab/tabpanel und Pfeiltastenbedienung. Die strenge Richtlinie bleibt Voreinstellung: öffentliche Seiten senden weiterhin script-src 'none', nur eine Seite mit Tab-Block sendet script-src 'self'. Inline-Skript ist nie erlaubt. In der Editorvorschau erscheinen Tabs untereinander, weil die Vorschau kein JavaScript ausführt; ein Hinweis im Eigenschaftenpanel sagt das.
  • Dokumentschema v6 mit zehn Blöcken für die Pilotseite: Symbolkarte, Kennzahl, Kundenstimme mit Bewertung, Logoleiste, Aufruf-Band, Kontaktdaten, Trennlinie, Abstand, Akkordeon und Akkordeon-Eintrag. Symbole als Inline-SVG aus einer festen Liste, kein Symbolschriftsatz und keine externe Anfrage. Kontaktdaten werden zu tel:- und mailto:-Verweisen. Akkordeon über details/summary, also ohne öffentliches JavaScript. Tabs sind bewusst nicht umgesetzt – ohne JavaScript gibt es dafür keine zugängliche Lösung; Begründung in ADR 015.
  • Beiträge: Datum, Autor, Auszug und Beitragsbild als Seitenspalten, gepflegt über ein Formular in der Seitenübersicht. Kategorien und Schlagwörter als Begriffe mit eigenen Archiven unter /kategorie/{slug} und /schlagwort/{slug}, Gesamtübersicht unter /beitraege mit Paginierung über ?seite=N, RSS unter /feed.xml. Der Block postlist (Dokumentschema v5) speichert nur Anzahl und Filter; die Beiträge stellt der Controller beim Ausliefern zusammen, damit ein gespeichertes Dokument keinen unveröffentlichten Beitrag zeigen kann.
  • Menüverwaltung unter /admin/menus für die Plätze Hauptnavigation und Fußzeile. Ein Eintrag verweist auf eine Seite, auf ein Sprungziel innerhalb einer Seite oder auf eine Adresse (HTTPS, lokaler Pfad, mailto:, tel:). Zwei Ebenen, bis 60 Einträge, optimistische Konflikterkennung. Bedienung über eine nummerierte Zeilenliste, vollständig ohne JavaScript; Untermenüs in der Ausgabe über details/summary. Seitenverweise speichern die Seiten-ID, nicht den Pfad – ein Eintrag folgt einer umbenannten Seite und überlebt eine gelöschte.
  • Dokumentformat v4: Blocktypen mit Namensraum (acme.slider) gelten als Bausteine einer Erweiterung und werden erhalten statt abgewiesen. Öffentlich rendert der Kern aus ihnen nichts außer einem HTML-Kommentar, in der Editorvorschau einen Platzhalter. Im Editor bleiben sie auswählbar, verschiebbar, duplizierbar und löschbar, aber nicht bearbeitbar. v1 bis v3 bleiben unverändert unterstützt.
  • Login-Throttling nutzt in der Testumgebung einen Array-Cache. Zuvor überdauerten die Zähler den Testlauf und sperrten die Anmeldung nach wenigen Wiederholungen aus, was als Testfehler erschien.
  • Lokale PHP-/Browser-/Lighthouse-Prüfungen und minimale GitHub-Actions-Konfiguration einschließlich vorbereitetem MySQL-Smokejob.

Geprüft

  • 201 PHP-Tests / 716 Assertions unter PHP 8.2.12 und 8.4.22 erfolgreich; zusätzlich 15 JavaScript-Modelltests, der vollständige Playwright-Editortest und die öffentliche Browserprüfung. Abgedeckt sind Migration/Erhaltung, Versionen, ungültige Geräte-/Typ-/Design-/Effektwerte, Vererbung/Reset/Undo, Vorlage-/Galerie-IDs, Uploadvalidierung und Neuencodierung, Metadatenkonflikte, Archivierung sowie sichere Bildquellen.
  • Neu geprüft (15 Tests): privater Entwurf nach dem Anlegen, verschachtelte Pfade mit gleichem Slug unter verschiedenen Eltern, abgewiesene Adressen (Großbuchstaben, Schrägstrich, leer, reserviert, unbekannter Typ, fehlendes Elternteil) ohne Schreibzugriff, doppelte Adresse, Tiefengrenze, Beiträge ohne Unterseiten, Löschen mit allen Revisionen samt Schutz für Elternseiten und veraltete Versionen, sowie dass die CMS-Routen nicht vom öffentlichen Auffangmuster verschluckt werden. Über HTTP zusätzlich: anonyme Schreibversuche, CSRF für Anlegen und Löschen, Adressableitung aus dem Titel, kein Löschangebot und HTTP 403 für die Editorrolle, erfolgreiches Löschen als Administrator.
  • Verwaltung geprüft: axe ohne Verstöße auf Anmeldung, Seitenübersicht und Editor (Desktop und Mobil), 201 PHP-Tests grün. Nicht geprüft: kein Screenreader, und die Bildschirme Menüs, Formulare, Bereiche, Design, Einstellungen und Fassungen sind nur von Hand angesehen, nicht automatisiert auf Barrierefreiheit geprüft – die Browserprüfung deckt sie nicht ab.
  • Aufgabenplan geprüft (14.09.2026): 12 Tests auf die Mechanik (nur Fälliges läuft; eine gesperrte Aufgabe wird übergangen, eine abgelaufene Sperre übernommen; ein Fehlschlag wird festgehalten und der nächste Versuch rückt weiter weg; eine abgeschaltete Aufgabe läuft nicht, von Hand gestartet doch; eine Anfrage arbeitet nicht alles auf einmal ab; eine fehlende Tabelle ist kein Fehler) und 5 über HTTP (der Bildschirm nennt jede Aufgabe und die Adresse des URL-Cronjobs; anonym führt niemand etwas aus, eine Redakteurin bekommt 403, ohne gültiges Formular passiert nichts; der Takt des Backends antwortet mit JSON; ein falscher Cron-Schlüssel bekommt 404). Von Hand auf der Entwicklungsinstallation alle vier Auslöser ausgeführt: alle fünf Aufgaben über die Konsole (Sicherung 36 ms, Updateprüfung 318 ms gegen das echte Repository), ein Seitenaufruf hat eine fällige Aufgabe abgearbeitet (Auslöser besuch im Protokoll), das geöffnete Backend hat getaktet (POST /admin/aufgaben/takt → 200), und der URL-Cronjob hat mit dem richtigen Schlüssel Text geliefert und mit einem falschen 404. Der Bildschirm wurde im Browser bedient (Abstand und Schalter gespeichert und wieder zurückgesetzt); dabei fiel eine Inline-Stilangabe auf, die die CSP der Verwaltung blockiert hat – sie ist jetzt eine Klasse.
  • Ladeverhalten geprüft: drei Tests – 30 Fassungen ergeben zwei Seiten mit richtiger Reihenfolge und richtigem Rest, eine Seite jenseits des Endes zeigt die letzte statt einer leeren; das erste Bild trägt loading="eager" und fetchpriority="high", das zweite lazy; und die Startseite bleibt unter zwölf Abfragen. Dazu das Ladebudget in der Browserprüfung.
  • Wiederherstellung nachgewiesen, nicht nur gebaut – das war der älteste offene Punkt aus Arbeitspaket I. Acht Tests gegen eine wegwerfbare Installation, darunter der eigentliche: Sicherung anlegen, Seite und Bilddatei löschen, zurückspielen, beides ist wieder da. Dazu von Hand auf der Entwicklungsinstallation: Testseite angelegt (5 Seiten), gesichert, gelöscht (4), zurückgespielt (5, Seite mit ihrer Adresse wieder da), Testseite entfernt (4). Ebenfalls geprüft: eine manipulierte Sicherung wird abgewiesen, eine aus einer neueren Fassung nur mit --force, Rotation behält die neuesten, und eine nach der Sicherung hochgeladene Datei überlebt die Wiederherstellung.
  • Seitenbereiche geprüft: Standard, anderer Bereich, „keiner“ (dann steht weder site-header noch site-footer im HTML, die Seite selbst aber schon), ein Verweis auf einen gelöschten Bereich fällt auf den Standard zurück, eine veraltete Fassungsnummer und eine ungültige Adresse werden abgewiesen. Menüarten: drei angenommen, eine erfundene abgewiesen, Burger als details – und die Seite behält script-src 'none'. Seitenbreite: die Regel in site.css nimmt den Theme-Wert, und ein größerer Wert erscheint als --k-container im Seiten-Stylesheet.
  • Vollbild geprüft: vier Abschnittsbreiten werden angenommen und eine erfundene abgewiesen; sieben Höhenangaben, darunter dvh, svh und lvh, und zwei ungültige; ein randloser Abschnitt mit formatfüllendem Bild erzeugt #id{width:100%} und #id img{object-fit:cover;height:100dvh}. Die Hero-Vorlage ist im JavaScript-Modelltest nachgerechnet, Eigenschaft für Eigenschaft.
  • Designer geprüft: 16 Datensätze prüfen, dass jede Eigenschaftsform genau die versprochene Deklaration erzeugt – Rahmen je Seite in der richtigen Schreibweise, Ecken, Positionierung, Flex, getrennte Abstände, Innenschatten, Textschatten, Filter in fester Reihenfolge, Mischmodus, und die Bild-Eigenschaften in einer zweiten Regel #id img{…}. Jeder davon muss außerdem den Validator überleben, sonst bietet der Editor etwas an, das nicht gespeichert werden kann. Ein Test hält Schema und Editorpanel zusammen: eine Gruppe, die das Panel nicht kennt, würde ihre Eigenschaften in einem unbenannten Aufklappfeld verstecken.
  • Neue Bausteine geprüft: vier Darstellungen (dekoratives Symbol vor Screenreadern verborgen, sprechendes Symbol mit Text, Fortschritt mit role="meter" und Wert im Markup, Slider mit Klasse je Gerät) und acht abgewiesene Eingaben (Symbolgröße außerhalb der Liste, unbekanntes Symbol, sprechendes Symbol ohne Beschriftung, dekoratives mit Beschriftung, Fortschritt über 100, Fortschritt als Text, Slider mit sieben Folien, Slider ohne Gerätangabe). Keiner der drei braucht ein Skript: die Seite behält script-src 'none'.
  • Updater geprüft: sechs Tests gegen echte Git-Repositorys in einem Temp-Verzeichnis – Vorprüfung benennt geänderte Dateien und eine nicht ausgenommene Datenbank, ein Update wechselt den Tag in der geprüften Reihenfolge und hebt den Wartungszustand auf, ein gescheiterter Schritt setzt Code und Datenbank zurück und bringt die Website auf dem alten Stand wieder online, ein Probelauf verändert nichts, ein unbekannter Tag wird beim Checkout abgewiesen, und eine bereits installierte Fassung ist kein Fehler. Zusätzlich am echten Repository durchgespielt (siehe QUALITY).
  • Statistik geprüft: 32 Tests, darunter die Spaltenliste der Tabelle gegen die Zusage „keine Spalte, aus der eine Person hervorgeht“, 14 Anfragearten (Verwaltung, Bild, Stylesheet, Feed, Weiterleitung, POST, Googlebot, Überwachungsdienst, Headless, curl – alle nicht gezählt), sieben Verweisformen (nur der Host bleibt, die eigene Domain gilt nicht als Verweis), sechs Adressformen (Query verworfen, erfundene 404 gesammelt) und das Verschmelzen und Löschen alter Tage. Dazu durch echte Anfragen: fünf Aufrufe werden ein Zähler, ein 404 zählt nicht als Aufruf, Verwaltung und Feed zählen nicht.
  • Einwilligung geprüft: 26 PHP-Tests, darunter sieben Cookiezustände (gewährt, abgelehnt, andere Gruppe, abgelaufen, ältere Fassung der Frage, Unsinn, unbekannte Gruppe) und zehn abgewiesene Einbettungsadressen (http, protokollrelativ, javascript:, data:, mit Port, mit Zugangsdaten, Leerzeichen im Host, ohne Titel, unbekannte Gruppe, zu geringe Höhe). Im Browsertest zusätzlich: Banner erscheint, kein iframe und keine Adresse im Markup, Einwilligen über den Platzhalter lädt den Inhalt, Widerruf hält ihn wieder zurück, axe ohne Verstöße auf Banner und Einwilligungsseite.
  • Verwaltung geprüft: axe ohne Verstöße auf elf Bildschirmen – Anmeldung, Seitenübersicht, Benutzer, eigenes Konto, Mediathek, Menüs, Formulare, Bereiche, Design, Einstellungen und Editor in Desktop und Mobil. Vorher waren es drei.
  • Benutzerverwaltung geprüft: 16 Tests, darunter zwei gleichzeitige Sitzungen, die zeigen, dass ein zurückgesetztes Passwort und eine geänderte Rolle die andere Sitzung abmelden, während die handelnde angemeldet bleibt. Acht abgewiesene Kontoanlagen (zu kurzes Passwort, mehr als 72 Bytes, Nullbyte, drei ungültige Namen, Doppelname, unbekannte Rolle) legen kein Konto an. Jeder Schreibweg braucht ein CSRF-Token und ist ohne Administratorrolle 403.
  • Mediathek geprüft: Upload über das Formular mit zwei Dateien in einem Absenden, CSRF-Pflicht, Bildangaben mit Versionskonflikt, Archivfilter, Blättern bei 26 Bildern (24 auf Seite eins, korrekte Sortierung neueste zuerst, ?seite=99 wird auf die letzte Seite begrenzt) und Rückkehr auf dieselbe Seite nach dem Speichern. Die Ablagefläche ist im Browsertest geprüft (gewählte Dateien erscheinen im echten Dateifeld und werden benannt) und von Hand mit einem echten Drop, bei dem eine Nicht-Bilddatei übersprungen wurde.
  • Updateverfahren geplant, nicht gebaut: docs/UPDATE.md beschreibt, wie eine neue Version über einen Git-Tag gezogen wird – Vorprüfung, Sicherung, Wartungszustand, Checkout, Migration, Rauchtest, Rückweg. Zwei Entscheidungen hängen am Hosting: Composer auf dem Server oder vendor/ im Tag, und ob es überhaupt Shell-Zugriff gibt.
  • Nicht geprüft: kein Screenreader.
  • Eigener Code geprüft: fünf CSS-Formen werden korrekt auf das Element bezogen, darunter & .block-text mit erhaltenem Nachfahren-Leerzeichen. Zehn abgewiesene CSS-Eingaben erreichen kein Dokument: Ausbruch mit }, fremder Selektor, @import, @media, javascript:-Adresse, protokollrelative Adresse, Backslash-Escape, expression(), Eigenschaft ohne Wert und unsinniger Eigenschaftsname. Eigenes CSS erscheint im Seiten-Stylesheet, eigenes Skript nur als Datei mit script-src 'self' ausschließlich auf dieser Seite; eine Seite ohne eigenes Skript behält script-src 'none' und liefert keine Datei. Ein Skript aus einem unveröffentlichten Entwurf wird nicht ausgeliefert. Der Rollenvergleich weist Ändern, Entfernen und Hinzufügen eines Skripts ab und lässt unveränderte Skripte durch.
  • Formatierter Text geprüft: dreizehn Fälle durch den Filter, darunter erlaubte Auszeichnung und Listen, ein script samt Inhalt entfernt und der Text danach erhalten, Ereignisattribut entfernt, class/style entfernt und das Element ausgepackt, javascript:-Link zu Text geworden, Bild mit onerror entfernt, iframe entfernt, unausgeglichene Auszeichnung geschlossen, lokaler Link erhalten, externer Link mit rel="noopener", Klartext maskiert und Umbruch zu <br>. Dazu: gefiltert wird beim Speichern (die Datenbank enthält schon die sichere Fassung), ein nach dem Filtern leerer Textblock wird abgewiesen ohne zu schreiben, ab v12 erscheint Auszeichnung als Auszeichnung in einem div, vor v12 maskiert in einem p, und in einem geteilten Bereich gilt derselbe Filter.
  • Bereiche geprüft: die eingesetzten Kopf- und Fußbereiche erscheinen als gewöhnliche Blöcke, der Navigationsblock zeigt das Menü, und eine Änderung am Kopfbereich erreicht beide Seiten gleichzeitig. Ein leerer Bereich rendert nichts statt einer kaputten Hülle. Verknüpfte Sektion geprüft: auf zwei Seiten sichtbar, die Seitendokumente enthalten nur die Adresse, eine Änderung wirkt an beiden Stellen, usedBy nennt beide Seiten, und nach dem Entfernen bleiben die Seiten heil und der Verweis erhalten. Abgewiesen werden: Hauptüberschrift in einem geteilten Bereich, Sektion mit Selbstbezug, zweiter Kopfbereich und das Löschen von Kopf- oder Fußbereich.
  • Startseite geprüft: eine andere veröffentlichte Seite wird zur Wurzel, antwortet unter /, ihre eigene Adresse leitet mit 301 dorthin, die bisherige Wurzel wird eine gewöhnliche Seite und ist wieder verschiebbar. Sitemap, strukturierte Daten und Menüauflösung folgen derselben Entscheidung. Solange eine Seite die Wurzel ist, werden Umbenennen und Papierkorb abgewiesen. Drei ungültige Wurzelangaben (nicht vorhanden, leer, Großbuchstaben) ändern nichts.
  • Öffentliche Browserprüfung wieder grün, inklusive der vier maschinenlesbaren Adressen über HTTP mit richtigem Inhaltstyp. Sechs Seiten-/Breitenkombinationen ohne Code-Verstoß; die redaktionelle Feststellung zu /grundlagen wird ausgegeben.
  • Lighthouse 13.4.1: je drei Läufe mobil und Desktop, alle fünf Kategorien 100 mit Startseite, Theme, gehashtem Stylesheet und Routerskript.
  • Seitenverwaltung geprüft: Verschieben ändert drei Adressen über zwei Ebenen, jede neue antwortet mit 200 und jede alte mit 301 auf das richtige Ziel, Slugs und Elternbeziehungen der Unterseiten bleiben erhalten. Vier Fälle werden abgewiesen, ohne etwas zu bewegen oder eine Weiterleitung zu erfinden: in die eigene Unterseite, in sich selbst, auf eine vergebene Adresse und mit einem unbrauchbaren Slug; dazu die Startseite. Papierkorb geprüft: Teilbaum geht mit, öffentlich 404, verschwunden aus Navigation, Sitemap, Archiv, Feed und Editorliste, Kind kann nicht vor dem Elternteil zurück, Wiederherstellen stellt nur die gewählte Seite zurück. Fassungen geprüft: Reihenfolge neueste zuerst, Wiederherstellen fügt eine Fassung hinzu statt zu überschreiben, eine fremde Fassung wird abgewiesen. Zeitplan geprüft: ein künftiger Zeitpunkt veröffentlicht nichts, ein vergangener genau einmal, danach ist der Zeitpunkt verbraucht, und eine Seite im Papierkorb wird nie veröffentlicht.
  • Der vollständige Browser-End-to-End-Test läuft wieder und deckt jetzt das neue System ab. npm run test:editor war durch den Umbau kaputt (er steuerte die abgeschafften Auswahllisten) und wurde auf die freien Bedienelemente umgeschrieben: Einheitenfelder, Vererbung über drei Geräte, einzelnes Zurücksetzen, Palettenfarbe, Abstand je Seite, Hover-Regel über den Template-Weg, freie Rasterspalten und die weiterhin vorhandenen Bewegungseffekte. axe meldet null Verstöße auf Login, Seitenliste, Editor in Desktop- und Mobilbreite, Medienauswahl und der bearbeiteten veröffentlichten Seite.
  • Lighthouse 13.4.1 auf der Startseite mit dem neuen Theme-System: je drei Läufe mobil und Desktop, alle fünf Kategorien 100. Das ist die erste Messung seit dem Umbau des Editors.
  • Im laufenden Editor durchgespielt: Doppelklick setzt das Element auf contenteditable="plaintext-only", eine Eingabe landet im Modell (Speicherstatus wechselt auf „Ungespeicherte Änderungen“, der Strukturbaum zeigt den neuen Text), und das Verlassen beendet die Bearbeitung sauber. Die Kontrastwarnung meldete bei #cccccc auf #ffffff korrekt „Kontrast 1.6:1 – zu wenig“. Die Schaltflächen zum Kopieren und Einfügen erscheinen mit richtigem Aktivierungszustand. Die Teständerungen wurden verworfen und nicht gespeichert.
  • Gestaltungsvorlagen über HTTP geprüft: anonymer Zugriff führt zur Anmeldung, CSRF-Token ist Pflicht, ein Stil mit eingeschleuster Deklaration und ein leerer Name werden mit HTTP 422 abgewiesen ohne zu schreiben, Speichern und Entfernen funktionieren, und das Designpanel erhält Vokabular, Palette und Vorlagen aus einer Quelle.
  • Freie Gestaltung geprüft: ein v10-Dokument mit Farben, Abständen je Seite, Ecken je Ecke, Schatten, Hover und eigenem Mobilwert wird unverändert gespeichert und zu blockgenauem CSS samt Media Query und Hover-Regel. Vierzehn abgewiesene Eingaben schreiben nichts: unbekannte Eigenschaft, unbekanntes Gerät, Farbe als Wort, Farbe als Funktion, Länge ohne Einheit, Länge als calc(), ein eingeschleustes ;position:fixed, Auswahlwert außerhalb der Liste, Zahl außerhalb des Bereichs, Seitenwert als Einzelwert, unbekannte Seite, Hover auf einer Eigenschaft ohne Hover, Schriftschlüssel mit Anführungszeichen und Hintergrundbild als Pfad. Zusätzlich geprüft, dass freie Stile Schema v10 verlangen.
  • Sicherheitsrichtlinie geprüft: das erzeugte Stylesheet wird über seinen eigenen SHA-256-Hash erlaubt, unsafe-inline kommt nicht vor und Skripte bleiben aus. Ohne Google-Schrift wird kein fremder Host erlaubt und keiner kontaktiert; mit Google-Schrift erscheinen fonts.googleapis.com in style-src und fonts.gstatic.com in font-src.
  • Theme geprüft: Palettenfarben werden zu CSS-Variablen und normalisiert, die Schriftvariable enthält Familie und Ersatz, Grundgröße, Zeilenhöhe und Inhaltsbreite wirken auf jeder Seite. Acht ungültige Themes schreiben nichts: Farbe als Wort, doppelter Schlüssel, Schriftname als Stapel, Google-Schrift ohne Namen, Grundfarbe ohne Paletteneintrag, Grundgröße ohne Einheit, Zeilenhöhe außerhalb des Bereichs und leere Palette.
  • Im Editormodell geprüft: Vererbung vom größeren Gerät, einzelnes Zurücksetzen, das Entfernen leerer Reste, Kopieren der Gestaltung ohne geteilte Objekte und ein vollständiges Undo auf den Ausgangszustand.
  • SEO geprüft: die Sitemap ist wohlgeformtes XML und enthält weder eine noindex-Seite noch einen Entwurf, dafür Beitragsübersicht und Begriffsarchiv; robots.txt verweist auf sie und sperrt /admin; eine nicht indexierbare Seite trägt zusätzlich ein noindex-Meta. Weiterleitungen antworten vor dem 404 mit 301 beziehungsweise 410, eine vorhandene Seite gewinnt gegen eine gleichnamige Weiterleitung, und Selbstbezug, Kette, Großbuchstaben sowie ein anderer Statuscode werden abgewiesen. Strukturierte Daten entstehen nur aus gepflegten Werten: ohne Angaben kein Block, danach LocalBusiness mit Anschrift und entdoppelten Profilen, Article mit Autor aus den Beitragsdaten und BreadcrumbList aus der Hierarchie. llms.txt antwortet ausgeschaltet mit 404 und listet eingeschaltet weder eine noindex-Seite noch einen Entwurf. Fünf ungültige Unternehmensangaben schreiben nichts, und in robots.txt überleben weder Markup noch eine zweite Sitemap-Zeile.
  • Formulare geprüft: Ausgabe ohne Skript, Honeypot außerhalb der Tabreihenfolge, gültige Absendung wird gespeichert und per HTTP 303 zurückgeleitet, Einwilligungszeitpunkt festgehalten, keine Absenderadresse in der Zeile sondern ein 64-stelliger Hash, Erfolgsmeldung auf der Zielseite. Fehlerhafte Absendung: HTTP 422, nichts gespeichert, vier Meldungen an den richtigen Feldern mit aria-describedby und aria-invalid, genau eine Zusammenfassung mit role=alert, alle Eingaben erhalten. Vier Spamfälle (gefüllter Honeypot, zu schnell, gefälschte Signatur, zu alter Zeitstempel) werden still verworfen und wie ein Erfolg beantwortet. Das Limit stoppt die sechste Absendung innerhalb einer Stunde. Die Formularverwaltung verlangt Anmeldung, Editorrolle und CSRF-Token und weist eine ungültige Empfängeradresse mit Meldung ab.
  • Tabs geprüft: beide Panels samt Inhalt und Überschrift stehen ohne JavaScript im HTML und sind serverseitig nicht versteckt, die Ankerliste verweist auf die Panels, das Skript wird als externe Datei geladen und nie inline. Die Richtlinie sendet script-src 'self' ausschließlich auf der Seite mit Tab-Block; eine Seite ohne Tabs und die 404-Seite behalten script-src 'none' und laden kein Skript. Ein Tab außerhalb einer Tabgruppe, eine Tabgruppe mit Textkind und Tabs in Schema v6 werden abgewiesen. Im Modell: eine neue Tabgruppe bringt zwei Tabs mit eigenen IDs mit, nimmt nur Tabs auf, und ein Tab lässt sich weder in eine Sektion noch in ein Akkordeon einfügen.
  • Neue Blöcke geprüft: eine Seite aus allen zehn Abschnittsarten wird unverändert gespeichert, veröffentlicht und ausgegeben; Kontaktdaten werden zu tel:- und mailto:-Verweisen mit bereinigter Nummer, das Akkordeon ist ein echtes details-Element, Symbole sind Inline-SVG, die Bewertung ist für Screenreader ausgeschrieben, und die Ausgabe enthält kein script. Vierzehn abgewiesene Eingaben schreiben nichts: unbekanntes Symbol, fehlender Titel, javascript:-Ziel, Bewertung außerhalb 0–5 und als Zeichenkette, leerer Kontaktblock, ungültige Telefonnummer und E-Mail-Adresse, unbekannte Abstandsgröße, unerlaubtes Linkschema, Text in Logoleiste und Akkordeon, Akkordeon-Eintrag ohne Akkordeon sowie Kinder an einer Trennlinie. Zusätzlich geprüft, dass die neuen Blöcke in Schema v5 weiterhin abgewiesen werden.
  • Beiträge geprüft: Sortierung nach Datum mit Paginierung und Begrenzung zu großer Seitenzahlen, Seiten und unveröffentlichte Beiträge bleiben aus dem Archiv, Kategorie- und Schlagwortarchive einschließlich getrennter Namensräume, erneutes Speichern ersetzt die Zuordnung statt sie zu ergänzen, ein leer gewordenes Archiv antwortet weiter mit HTTP 200 und leerer Liste, Löschen löst die Zuordnungen und lässt die Begriffe bestehen, Veröffentlichen stempelt ein fehlendes Datum und lässt ein gepflegtes unangetastet, Seiten werden nie gestempelt. Sechs abgewiesene Metadateneingaben schreiben nichts und erhöhen die Version nicht. Die Beitragsliste zeigt nur Beiträge des gefilterten Begriffs, und das gespeicherte Dokument enthält weiterhin ausschließlich Anzahl und Filter. Der Feed ist wohlgeformtes XML mit absoluten Adressen und enthält keinen unveröffentlichten Beitrag.
  • Menüs geprüft: Seiten-, Anker- und externe Ziele einschließlich tel: in der Ausgabe, aria-current nur auf der tatsächlich aktuellen Seite und nicht auf Ankereinträgen, gelöschte und unveröffentlichte Seiten fallen aus der Navigation ohne die übrigen Einträge zu verlieren, Kinder eines übersprungenen Eintrags rücken nach, gespeicherte Einträge bleiben erhalten und erscheinen nach dem Veröffentlichen wieder. Zehn abgewiesene Eingaben (leere und überlange Beschriftung, unbekannte Zielart, fehlende Seite, ungültige Sprungziele, javascript:, http:, protokollrelative Adresse, dritte Ebene) schreiben nichts. Über HTTP zusätzlich: anonymer Schreibversuch, CSRF, veraltete Version, Aufbau des Baums aus flachen Formularzeilen mit Position und Ebene, normalisiertes Sprungziel und die Bestätigung, dass die Navigation nicht mehr aus dem Template stammt.
  • Plugin-Erhalt geprüft: ein v4-Dokument mit acme.slider samt verschachteltem Text wird Byte für Byte gespeichert, veröffentlicht und wieder geladen; weder Props noch Kindinhalte noch eine javascript:-URL erscheinen in der öffentlichen Ausgabe, die übrige Seite rendert normal. Fehlerhafte Namensräume und Plugin-Blöcke in v1 bis v3 werden weiterhin abgewiesen, ohne zu schreiben. Im JavaScript-Modell zusätzlich: Verschieben, Duplizieren mit neuen IDs, unverändertes Undo, kein Einfügen in den Baustein und keine Erzeugung über die Elementbibliothek.
  • Testumgebung: tests/bootstrap.php verwirft var/cache/test einmal pro Lauf. Ohne Debug prüft Symfony weder den kompilierten Container noch die Vorlagen auf Aktualität; das hat zweimal ein falsches Ergebnis erzeugt (eine Vorlagenänderung wurde übergangen, später schlug ein geänderter Konstruktor aus einem veralteten Container fehl). Zusätzlich lädt Twig in der Testumgebung automatisch neu.
  • Migration 20260912000600 zuerst gegen eine Kopie der lokalen Datenbank ausgeführt und das Ergebnis kontrolliert: acht Anweisungen, vorhandene Seiten und Dokumente unverändert, neue Tabellen term und page_term, Sortierindex angelegt. Danach nach Backup unter var/backups/before-posts-*.db lokal angewendet.
  • Migration 20260912000500 zuerst gegen eine Kopie der lokalen Datenbank ausgeführt, dann nach Backup unter var/backups/before-menus-*.db lokal angewendet. Seeding legt fehlende Menüs an und lässt vorhandene unverändert; zweimal hintereinander ausgeführt.
  • Migration 20260912000400 zuerst gegen eine Kopie der lokalen Datenbank geprüft und die erzeugten Anweisungen kontrolliert: vier reine ALTER TABLE … ADD COLUMN, kein Tabellenneuaufbau. Danach nach konsistentem Backup unter var/backups/before-hierarchy-20260912-102402.db lokal angewendet; Backfill, Indexumstellung und unveränderte Dokumente überprüft. Wiederholtes Seeding und Produktionsbuild erfolgreich.
  • Composer-/Format-/Twig-/YAML-/Containerprüfungen, Migration, wiederholtes Seeding und Produktionsbuild erfolgreich.
  • Frische Quellkopie ohne bestehende DB/Secrets/Dev-Abhängigkeiten erfolgreich eingerichtet; gespeicherte Seite als HTTP 200 gerendert.
  • Browserprüfung aller drei öffentlichen Seiten mobil und Desktop; keine axe-Verstöße, keine Überläufe, funktionierende Sprunglink-/Navigationsabläufe auch ohne JavaScript.
  • Editor-End-to-End in isolierter Installation: Anmeldung, Auswahl im Iframe, Texteingabe, Undo/Redo, Einfügen/Duplizieren/Verschieben/Drag-and-drop/Löschen, identisches Neuladen, private Entwürfe, Veröffentlichung, Dialog-Fokus/Escape, simulierter Netzwerkfehler mit Wiederholung, 409-Konflikt mit JSON-Export und Server-Neuladen. Einschränkungen der axe-Frameprüfung sind in QUALITY dokumentiert.
  • Ausbau im Browser geprüft: Struktursuche und Auswahl per Tastatur, Einklappen, Abstände, Schriftwerte und Vererbung in echten Gerätebreiten, Rücksetzen/Undo, responsive Zweispaltenvorlage und identische Styles nach Speichern/Publizieren auf allen drei Geräten.
  • Editoroberfläche, Login und Seitenübersicht ohne axe-Verstöße; identisch gerenderter bearbeiteter öffentlicher Inhalt separat geprüft. Desktop- und Mobil-Screenshots visuell durchgesehen.
  • Lighthouse-Startseite: jeweils drei echte mobile und Desktop-Läufe, Median in allen fünf Kategorien 100. Nur lokaler Prototyp; keine Kundenwebsite-Abnahme. Umfang und Grenzen siehe QUALITY.
  • Gestaltete v2-Testseite mit Design-CSS und responsiver Abschnittsvorlage: zusätzliche sechs Lighthouse-Läufe, alle fünf Kategorien jeweils 100; LCP-Median mobil 901 ms/Desktop 240 ms, CLS/TBT 0. Echte Seitenbestände und geöffnete Browserentwürfe unangetastet. Bericht: docs/quality/editor-design-lighthouse.json.
  • v3-Galerieseite mit echten optimierten Testbildern und Effekten: sechs Lighthouse-Läufe, alle fünf Kategorien jeweils 100; LCP-Median mobil 937 ms/Desktop 279 ms, CLS/TBT 0. Upload, Metadaten, Mehrfachauswahl per Tastatur, Bildbeschriftung, Sortier-Undo, Archivieren/Wiederherstellen, Bewegungsreduktion und öffentliche Bilder im isolierten Browserablauf bestanden. Keine axe-Verstöße in Desktop-/Mobil-Mediathek; Screenshots geprüft. Berichte: docs/quality/editor-media-browser.json und editor-media-lighthouse.json.
  • Keine bekannten Dependency-Advisories im ausgeführten Composer-Audit/npm-Installationsaudit.

Gemessen am Nachbau selbst (13.09.2026, Fassung 1.4.3): je drei mobile und drei Desktop-Läufe auf /, /projekte und /impressum im Produktionsmodus, Median. Barrierefreiheit, Best Practices, SEO und agentisches Browsen überall 100. Leistung: /projekte und /impressum 100/100, die Startseite 99 mobil / 97 Desktop – dort bleibt das blockierende Stylesheet (rund 300 ms erste Anzeige) das Letzte, und das ist zu einem guten Teil der Entwicklungsserver ohne Kompression und Cache-Kopfzeilen. Bericht: docs/quality/kantit-lighthouse.json. Auf dem echten Hosting nachmessen – dort greifen die Regeln aus public/.htaccess.

Bekannte Einschränkungen

  • Gestaltung über geprüfte Tokens, keine beliebigen Pixelwerte, eigenen Schriften oder freien Farben. Noch keine Inline-Textbearbeitung, Formulare, dynamischen Menüs, Plugin-/Themeverwaltung oder WordPress-Import. Abschnittsvorlagen sind lokale Blockrezepte, keine globale Templateverwaltung.
  • Mediathek derzeit für statische Bilder und PDFs (seit 22.09.2026): keine Videos/SVGs, Lightbox/Slideshow, freie Bildzuschnitte, Ordner, Paginierung oder physische Löschung. Bild-URLs sind öffentlich; Archivieren schützt keine vertraulichen Dateien. Originale werden durch optimierte Varianten ersetzt. Einblendungen starten beim Laden, nicht beim Scrollen.
  • Sektionen bieten die vorhandenen Farb-/Breitentokens geräteübergreifend. Benutzerverwaltung und Passwortänderung sind noch nicht als Browseroberfläche verfügbar.
  • Der Aufgabenplan ist von Anfragen abhängig. Ohne Besucher und ohne geöffnetes Backend läuft nichts; minutengenau ist nur ein echter Cronjob oder der URL-Cronjob des Hosters. Unter Apache mit mod_php belegt eine laufende Aufgabe einen PHP-Prozess, nachdem die Seite beim Besucher ist (dort gibt es kein fastcgi_finish_request()) – deshalb sind die Grenzen je Seitenaufruf klein (2 Aufgaben, 20 Sekunden) und der Auslöser abschaltbar. Die Sicherung ist die schwerste Aufgabe und läuft trotzdem mit: eine tägliche Sicherung ist mehr wert als keine. Nicht geprüft: das Verhalten unter mod_php und unter echter Last – hier lief alles gegen den eingebauten PHP-Server.
  • Offene redaktionelle Feststellung: /grundlagen trägt einen im Editor gesetzten Textblock in #d08080 auf hellem Grund – 2,75:1, unter der AA-Schwelle. Die Farbe wurde nicht verändert, weil sie eine Nutzerentscheidung ist; die Prüfung nennt sie bei jedem Lauf, und die Kontrastwarnung im Editor meldet sie ebenfalls.
  • Aus Umuts erweitertem Auftrag noch offen (Paket G2 im Ausbauplan): die Neugestaltung der Verwaltung.
  • Eigenes CSS kennt keine Gerätevarianten – dafür sind die Vorschaugrößen im Designbereich da. Eigenes Skript hat keine Fassungsverwaltung außer der Seitenhistorie und keine Prüfung auf Syntaxfehler; ein fehlerhaftes Skript schreibt in die Browserkonsole und lässt die Seite sonst unangetastet.
  • Formatierter Text gilt nur für Textblöcke. Überschrift, Button, Zitat und die Titel von Karten, Tabs und Akkordeon bleiben Klartext; Auszeichnung in einer Überschrift bringt mehr Ärger als Nutzen. Die Werkzeugleiste ist nicht mit einem Screenreader geprüft.
  • Offen: der Papierkorb wird nicht automatisch geleert, es gibt keine Aufbewahrungsfrist, und Weiterleitungen aus einem Verschiebevorgang lassen sich nur einzeln in den Einstellungen entfernen.
  • Weiterhin keine Erprobung mit einem Screenreader. Bei der Direktbearbeitung wiegt das: dass der Fokus sinnvoll wandert und der Zustandswechsel angekündigt wird, ist durch axe nicht abgedeckt.
  • Gestaltungsvorlagen lassen sich in der Oberfläche nur anlegen und anwenden, nicht umbenennen; das Entfernen geht bisher nur über die Schnittstelle. Es gibt keine Vorschau einer Vorlage.
  • SEO-Titel und -Beschreibung sind weiterhin die Dokumentfelder; getrennte SEO-Titel gibt es nicht. Das Vorschaubild wird über seine Mediathek-ID eingetragen und noch nicht als og:image aufgelöst. Weiterleitungen werden nur bei genauer Übereinstimmung des Pfades gefunden, ohne Muster und ohne Berücksichtigung von Abfrageparametern; Seiten umbenennen erzeugt noch keine automatische Weiterleitung (Paket F).
  • Dateiuploads in Formularen fehlen – Anhänge brauchen einen nicht öffentlichen Ablageort mit Zugriffsschutz und eine eigene Typprüfung; Begründung in ADR 017. Ebenfalls offen: Formulare lassen sich nicht löschen oder umbenennen, Einsendungen nicht exportieren oder löschen, und es gibt keine Aufbewahrungsfrist. Der tatsächliche E-Mail-Versand ist nie ausgeführt worden: die Tests nutzen einen Transport, der nichts sendet.
  • Das Tab-Skript ist nicht im Browser geprüft: Pfeiltastenbedienung, aria-selected, das Verstecken der Panels und die Auswahl über ein Sprungziel sind nur im Code umgesetzt, nicht ausgeführt. Geprüft ist bisher ausschließlich die serverseitig erzeugte Auszeichnung und die Sicherheitsrichtlinie.
  • Die neuen Blöcke lassen sich nicht in einer Vorlage bündeln: die drei Abschnittsvorlagen sind unverändert. Die Logoleiste hat keine Graustufen- oder Größenoption, die Kundenstimme kein Bild, die Symbolkarte keine eigene Farbe.
  • Beiträge haben noch keine Vorschau des Beitragsbilds in der Verwaltung: das Bild wird über seine Mediathek-ID eingetragen. Es gibt keine Begriffsverwaltung (Umbenennen, Zusammenführen, Löschen ungenutzter Begriffe), keine Beitragssuche und keine Archivbeschreibungen. Die Paginierung der Übersicht ist auf zehn Beiträge je Seite festgelegt.
  • Menüs kennen nur die beiden festen Plätze Hauptnavigation und Fußzeile; weitere Plätze, eigene Menüs pro Seite und automatisch aus der Hierarchie erzeugte Einträge gibt es nicht. Die Einträge werden über Position und Ebene sortiert, nicht per Drag-and-drop.
  • Seiten lassen sich anlegen und löschen, aber noch nicht umbenennen oder verschieben: eine Adressänderung müsste die Pfade aller Unterseiten mitziehen und Weiterleitungen anlegen. Beiträge werden gespeichert und ausgegeben, haben aber noch keine Übersichtsseite, keine Sortierung nach Datum und keine strukturierten Daten. Die Hauptnavigation ist weiterhin fest im Template; verschachtelte Seiten erscheinen dort nicht automatisch.
  • Für diesen Schritt wurden bewusst keine Browser-, Lighthouse- oder axe-Läufe ausgeführt (Nutzerentscheidung). Oberfläche und Ausgabe der neuen Formulare sind nur über die Funktionstests geprüft, nicht visuell.
  • SQLite tatsächlich getestet. MySQL-/MariaDB-Kompatibilität und CI erst vorbereitet, nicht nachgewiesen; Anbieter-/Tarifdetails und Livebetrieb offen.
  • Der Erhaltungsvertrag für Plugin-Blöcke steht, ein Pluginsystem nicht: Registrierung, Hooks, Abhängigkeiten und Ressourcenladen folgen in Phase 4. Kinder eines Plugin-Blocks werden gespeichert, aber nicht gerendert. Kern-Designtokens gelten für sie nicht.
  • Kein Inhaltscache, nur Symfony-Programmcache. Seiten senden no-store; Veröffentlichung ist dadurch ohne Cacheinvalidierungsverzögerung sichtbar.
  • Noch keine repräsentativen Kundeninhalte/Bilder/Blog-/Formularseite, keine Felddaten und keine vollständige manuelle Barrierefreiheitsprüfung.
  • Die vor der Hostingantwort erzeugte .venv bleibt ungenutzt und von Git ausgeschlossen. Die automatische Freigabeprüfung blockierte ihren Löschversuch mit „blocked by policy“; keine Umgehung versucht. Auch var/fresh-check, DB, Secrets und Berichte sind lokal ignoriert.

Arbeitspaket H, Teil 1: die Vorschau wird zur Arbeitsfläche

Erledigt. Der Abgleich mit dem Flatsome UX Builder steht in docs/FLATSOME-ABGLEICH.md: die Gestaltung ist Flatsome bereits voraus (93 freie Eigenschaften je Gerät und für Hover gegen feste Optionslisten), die Lücken liegen beim Elementkatalog (20 offene Elemente ohne Shop) und bei der Bedienung. Umuts Entscheidung: Bedienung zuerst, Shop-Elemente ausgelassen.

Umgesetzt ist die erste Hälfte der Bedienung:

  • Werkzeugleiste am Element in der Vorschau: Name des Elements, Elternelement auswählen, nach oben, nach unten, duplizieren, löschen. Sie trägt den Namen des Elements („Sektion duplizieren“) statt des nackten Verbs – die Seitenleiste hat dieselben fünf Aktionen, und zwei gleichnamige Bedienelemente auf einem Bildschirm sind für eine Sprachausgabe nicht unterscheidbar. Beide Wege rufen dieselben Funktionen auf.
  • Auswahl- und Überfahrrahmen mit Beschriftung direkt in der Vorschau.
  • Einfügepunkte: ein Pluszeichen an der Ober- und Unterkante des gewählten Elements setzt eine Marke; das nächste Element aus dem Werkzeugkasten landet genau dort statt am Ende.
  • Ziehen aus dem Werkzeugkasten in die Vorschau, mit Ablegemarke zwischen den Elementen und Rahmen um einen Behälter, der das Element aufnimmt. Der Strukturbaum zieht in dieselbe Fläche.
  • Im Modell dafür neu: insertBlockAt, moveBlockTo und dropList – punktgenaues Einfügen und Verschieben statt „hinter das Gewählte“.

Die Ebene liegt im Editordokument über dem Rahmen, nicht darin: die Vorschau wird bei jeder Änderung vollständig ersetzt, und eingezeichnete Bedienelemente wären damit weg und stünden außerdem in der Auszeichnung, die die Vorschau wahrheitsgetreu zeigen soll. Sie trägt denselben Skalierungsfaktor wie der Rahmen; Leiste und Pluszeichen werden gegenskaliert und bleiben in der Tablet- und Mobilvorschau fingergroß.

Geprüft. npm test 22 von 22 (vier neue Tests für Einfügen, Verschieben, Behälterregeln und die oberste Ebene). php vendor/bin/phpunit 434 von 434. npm run test:editor vollständig, mit einem neuen Abschnitt für Werkzeugleiste, Einfügepunkt und Ziehen; alle axe-Prüfungen ohne Verstoß, auch die mobile Editoransicht. Screenshot unter reports/editor/canvas-overlay.png.

Dabei gefunden und behoben: die ganze Ebene war zunächst aria-hidden und enthielt fokussierbare Schaltflächen – axe hat das gemeldet. canContain las block.type an einem fehlenden Block und zerbrach beim Ziehen aus dem Werkzeugkasten, wo der Block noch nicht existiert. Beim Verschieben ans Listenende wurde der Index begrenzt, bevor die Entnahme verrechnet war; der Block landete eine Stelle zu früh.

Bekannte Grenzen. Die Vorschau wird weiterhin nach 250 ms serverseitig neu erzeugt – das Ablegen fühlt sich dadurch nicht so unmittelbar an wie bei Flatsome, die Wahrheit bleibt aber der Server. Die Werkzeugleiste ist nicht mit einem Screenreader erprobt, nur mit axe. Spalten lassen sich noch nicht in der Vorschau breiter ziehen, der Werkzeugkasten ist weiterhin eine Textliste ohne Vorschaubilder, und es gibt je Option noch keinen eigenen Geräteschalter.

Arbeitspaket H, Teil 2: der Werkzeugkasten kennt den Zusammenhang

Erledigt. Umuts Einwand: Zeile und Spalte standen oben in der Auswahl, als wären sie Seitenanfänge. Sie gehören in den Inhalt – erst eine Sektion, dann eine Zeile darin. Das war nicht nur eine Anzeigefrage: eine Zeile nahm bisher jedes Kind an, eine Überschrift konnte neben den Spalten landen, und eine Spalte durfte überall stehen. Die Struktur, die der Editor in seinem eigenen Hinweistext beschreibt – Sektion → Zeile → Spalte → Inhalt –, galt für die Schaltflächen nicht.

Übernommen ist Flatsomes Regelmodell, weil es die Fälle abdeckt und weil es das ist, was Umut in den Händen hat (inc/builder/core/server/src/Collections/Elements.php):

Regel Flatsome KantSites jetzt
allow row => ['col'], accordion => ['accordion-item'] ALLOW – Zeile nimmt nur Spalten
require col => ['row'], tab => ['tabgroup'] REQUIRE – Spalte lebt nur in einer Zeile
— (Flatsome kennt das nicht) ROOT_NEEDS_CONTAINER – eine Zeile ist kein Seitenanfang

Der dritte Punkt ist der Zusatz: Flatsome lässt eine Zeile auch lose am Dokumentanfang zu. Genau das ist das „aus dem Nichts“, das Umut stört, also wird es unterbunden – in jedem Behälter dagegen bleibt die Zeile erlaubt, auch in einem Tab oder einem Akkordeon-Eintrag.

Daraus folgt die Bedienung:

  • Der Werkzeugkasten ist nach Aufbau gruppiert – Aufbau, Inhalt, Bausteine, Gruppen, Seite und Daten – statt einer flachen Liste aus 31 Typen.
  • Was an der gewählten Stelle nicht hingehört, ist gesperrt und sagt beim Überfahren warum („Spalte: gehört in Zeile. Wähle dort zuerst ein Element aus.“). Eine Regel, die man erklärt bekommt, lernt man; eine Schaltfläche, die nur nicht mehr reagiert, drückt man weiter.
  • Was möglich ist, sagt wohin es kommt („Wird in Zeile eingefügt.“).
  • Die Zeile wird nach Spaltenzahl angeboten (2, 3, 4) und bringt ihre Spalten mit. Der separate Zeilenbereich über der Liste ist entfallen.
  • Eine Regel für alle Wege: insertionTarget beantwortet „wohin käme das?“, ohne etwas einzufügen. Knopf, Einfügepunkt, Ziehen und die Sperrlogik des Werkzeugkastens nutzen dieselbe Funktion – vorher entschied insertBlock allein, niemand konnte vorher fragen, und insertRow hatte eine eigene zweite Kopie derselben Platzierungslogik.

Ein Element, das der gewählte Behälter nicht nimmt, wird nicht gesperrt, sondern steigt auf, bis ein Elternteil es annimmt: eine Überschrift bei gewählter Zeile landet hinter der Zeile in der Sektion. Die Absicht ist eindeutig, nur die Zeile ist der falsche Ort.

Nebenbei behoben: postnav fehlte seit Schema v18 in labels und icons – der Strukturbaum zeigte für die Beitragsnavigation „Unbekannt“ und ein Fragezeichen.

Geprüft. npm test 27 von 27 (fünf neue: Zeilen-/Spaltenregel, oberste Ebene, Übereinstimmung von Auskunft und Tat, Kategorie-Vollständigkeit, Zeile mit Spalten an fester Stelle). php vendor/bin/phpunit 446 von 446. npm run test:editor vollständig mit neuem Abschnitt „the toolbox follows the structure“; alle axe-Prüfungen ohne Verstoß. Screenshot unter reports/editor/toolbox.png.

Bekannte Grenzen. Inhalt darf weiterhin lose am Dokumentanfang stehen – der Validator erlaubt es, und eine weitere Einschränkung wäre erfunden statt übernommen. Bestehende Dokumente werden nicht nachträglich geprüft: eine Überschrift, die früher direkt in einer Zeile gelandet ist, wird weiterhin ausgegeben und lässt sich auswählen und löschen, nur nicht mehr dorthin verschieben. Der Werkzeugkasten hat weiterhin keine Vorschaubilder und keine Suche.

Arbeitspaket H, Teil 3a: die Optionen selbst

Erledigt. Drei Bedienungen, die Flatsome an jeder Option hat und die hier fehlten.

Geräteschalter je Option. Bisher entschied ein einziger Schalter über der Vorschau, welches Gerät das ganze Bedienfeld bearbeitet. „Ist die Überschrift auf dem Telefon kleiner?“ ließ sich nur durch Umschalten und Hinsehen beantworten. Jetzt trägt jede Option drei Chips (D, T, M): sie zeigen, wo ein eigener Wert liegt, und ein Klick wechselt dorthin. Das ist mehr als Flatsome bietet – dort schaltet man um, hier sieht man vorher, ob es überhaupt etwas zu sehen gibt. Bedienfeld und Vorschau wechseln gemeinsam: einen Wert für ein Gerät zu setzen, das man nicht ansieht, ist blindes Ändern.

Abstand als Rahmen. Innenabstand, Außenabstand, Rahmenstärke und Ecken standen als vier Felder untereinander, beschriftet „Oben, Rechts, Unten, Links“ – man musste lesen, um zu wissen, welche Kante man setzt. Jetzt sitzt jedes Feld an seiner Kante, die Lage ist die Beschriftung. In der Mitte verkettet ein Schalter alle vier. Er startet aus: er ersetzt den früheren Knopf „Alle Seiten gleich setzen“, und das war eine bewusste Handlung. Die Einheit bleibt eine Wahl für die ganze Eigenschaft – vier Einheitenmenüs passen nicht in ein 240 Pixel breites Feld. Seiten mit unterschiedlichen Einheiten werden als „gemischt“ geführt und bleiben unangetastet, bis eine Einheit tatsächlich gewählt wird; ein gespeichertes 2rem wird nicht still zu 2px.

Ziehfelder. Zahlenfelder lassen sich seitwärts ziehen. „24“ tippen, schauen, „26“ tippen, schauen, „22“ tippen ist drei Anläufe für eine Entscheidung; Ziehen beantwortet sie in einer Bewegung. Umschalt grob, Alt fein. Tippen und Pfeiltasten bleiben unverändert. Nur für Maus und Stift: auf einem Tippgerät ist ein seitliches Ziehen über ein Feld die Art, wie das Bedienfeld gescrollt wird.

Dabei gefunden und behoben: Der Zug war zunächst nach einem Pixel zu Ende – jede Änderung zeichnete das Bedienfeld neu und ersetzte damit genau das Feld, an dem der Zeiger hing. Das Bedienfeld bleibt während eines Zuges stehen und wird einmal neu gezeichnet, wenn losgelassen wird (keepPanel). Der Kettenschalter verlor aus demselben Grund seinen Zustand, sobald er gedrückt wurde; er liegt jetzt außerhalb der Darstellung. Und das Gleichziehen nahm den Wert des ersten Feldes, auch wenn es leer war – ein Druck auf die Kette löschte damit die Seiten, die einen Wert hatten. Es nimmt jetzt den ersten vorhandenen Wert.

Geprüft. npm test 27 von 27. php vendor/bin/phpunit 452 von 452. npm run test:editor vollständig mit neuem Abschnitt „device chips, spacing box and scrub fields“: Chips zeigen den richtigen Zustand und wechseln Bedienfeld und Vorschau zusammen, der Rahmen ändert ohne Kette nur eine Seite und mit Kette alle vier, die Einheitenumstellung erhält die Zahlen, und ein Zug über 20 Pixel hebt 72 auf 92 und ist ein Undo-Schritt. Alle axe-Prüfungen ohne Verstoß. Screenshot unter reports/editor/options.png.

Bekannte Grenzen. Die Chips zeigen einen eigenen Wert, aber nicht, welchen – dafür muss man weiterhin hinwechseln. Auf Tippgeräten gibt es kein Ziehen. Der Rahmen zeigt geerbte Werte als Platzhaltertext, nicht als eigene Kennzeichnung. Noch offen aus Teil 3: Vorschaubilder und Suche im Werkzeugkasten, Vorlagen je Element mit Bild, und das Ziehen der Spaltenbreite in der Vorschau.

1.5.3: der Editor blieb nach dem Update schwarz

Der Fehler. Nach 1.5.2 meldete der Editor im Browser The requested module './editor-model.js' does not provide an export named 'dropList', und der Werkzeugkasten war leer.

Ursache war nicht der Code, sondern das Zwischenspeichern. public/.htaccess setzt für jede .js-Datei Cache-Control: public, max-age=31536000, immutable – ein Jahr. Die Fassungskennung ?v= hängt aber nur an den Einstiegsdateien, die die Vorlage schreibt. Die fünf Module, die der Editor über import './…​.js' lädt, tragen keine: der Browser holte das neue editor.js und behielt das alte editor-model.js. Ein neues canvas-overlay.js traf damit auf ein Modell ohne dropList.

Beide gemeldeten Symptome hatten diese eine Wurzel: seit 1.5.1 baut das Skript den Werkzeugkasten auf (buildLibrary()), und editor.js importiert canvas-overlay.js in Zeile 5. Bricht der Import, läuft keine Zeile danach – und die Elementliste bleibt leer. Das gemeldete „Menü fehlt“ war der Werkzeugkasten, nicht die Verwaltungsnavigation. Die wurde geprüft: in Chrome 152 stehen ::details-content, ausgeblendeter Schalter und sichtbare Liste korrekt.

Die Behebung. Eine Importkarte in base.html.twig hängt die Fassung an die aufgelöste Adresse jedes Moduls, ohne dass eine einzige Importzeile sie kennen muss. Das behält das Zwischenspeichern über ein Jahr – die Leistung war nie das Problem, nur die fehlende Kennung.

Eine Importkarte muss eingebettet sein; die Spezifikation kennt keine externe. Unter script-src 'self' wurde sie wortlos verworfen. AdminHeadersSubscriber erzeugt deshalb je Verwaltungsseite einen Nonce, nennt ihn in der Richtlinie und legt ihn in die Anfrage; die Twig-Funktion csp_nonce() gibt ihn der Karte mit. Der Nonce erlaubt genau dieses eine Skript – 'self' gilt unverändert für alles andere.

Geprüft. Der Browsertest schreibt jede angeforderte .js-Adresse mit und verlangt an jeder eine Fassungskennung; vor der Behebung nennt er die fünf fehlenden beim Namen, danach trägt sie alle acht. Dazu ein Test, der jeden ./…​.js-Import in public/assets gegen die Importkarte hält – ein neues Modul, das dort fehlt, fällt damit im Test auf und nicht erst beim nächsten Update beim Kunden. Gegenprobe gemacht: mit einem entfernten Eintrag schlägt er fehl. npm test 28 von 28, php vendor/bin/phpunit 452 von 452, npm run test:editor vollständig.

Bekannte Grenze. Ein Modulfehler legt den Editor weiterhin vollständig lahm, und der Werkzeugkasten ist seit 1.5.1 nichts ohne sein Skript. Für einen Editor ist das vertretbar – die öffentliche Website läuft ohne JavaScript –, aber die Fehleranzeige dafür ist noch die Browserkonsole und kein Satz in der Oberfläche.

Arbeitspaket H, Teil 3b: der Werkzeugkasten bekommt Bilder und eine Suche

Erledigt. Die Elementwahl war eine Liste aus Schriftzeichen – „▣“ für Sektion, „☰“ für Zeile. Jetzt trägt jeder der 34 Einträge eine Strichzeichnung, die zeigt, was er baut: die Sektion ein gefülltes Band, „Zeile, 3 Spalten“ tatsächlich drei Kästen, das Bild einen Rahmen mit Bergzug.

Zeichnungen statt Bilddateien. Flatsome liefert je Element ein PNG mit. Gezeichnet ist hier besser: library-previews.js ist ein paar hundert Byte statt eines Ordners voller Bitmaps, die Formen nehmen die Farbe der Fläche an, auf der sie liegen – gesperrte Einträge werden damit von selbst blass – und sie bleiben auf jedem Bildschirm scharf. Die Zeilenvorschau wird gerechnet, nicht gezeichnet: zwei, drei oder vier Kästen aus derselben Funktion.

Suche. Ein Feld über der Liste filtert über alle Gruppen. Gesucht wird über den Namen, den internen Typ und den Gruppennamen – wer „slider“ eingibt, muss nicht wissen, dass er unter „Gruppen“ einsortiert ist. Gruppen ohne Treffer verschwinden, damit die Liste keine Lücken behält, und darunter steht, wie viele von wie vielen übrig sind.

Dabei bemerkt: Es gab damit zwei Suchfelder mit dem Namen „Element suchen“ auf einem Bildschirm – für eine Sprachausgabe nicht unterscheidbar. Sie heißen jetzt nach dem, was sie durchsuchen: „Element zum Einfügen suchen“ im Werkzeugkasten, „Element auf der Seite suchen“ im Strukturbaum.

Geprüft. Der Browsertest zählt die Vorschaubilder gegen die Einträge (alle 34 haben eines), sucht nach „galerie“ (ein Eintrag, eine Gruppe), nach „aufbau“ (fünf Einträge über den Gruppennamen), nach etwas Nichtvorhandenem und wieder nach nichts. npm test 28 von 28, php vendor/bin/phpunit 452 von 452, npm run test:editor vollständig ohne axe-Verstoß.

Noch offen aus Teil 3. Vorlagen je Element mit Vorschaubild. Und das Ziehen der Spaltenbreite: das geht heute nicht ohne einen Zwischenschritt, denn eine Zeile ist ein CSS-Raster mit gleich breiten Spalten (repeat(n, minmax(0,1fr))); ein span je Spalte gibt es nicht. Dafür braucht es erst ein Zwölf-Spalten-Modell im Designtoken-Manifest, und das berührt die öffentliche CSS und damit die Messwerte aus AGENTS.md. Das ist ein eigener Schritt, kein Nebenbei.

1.5.5: die Verwaltungsnavigation war am Schreibtisch unerreichbar

Der Fehler. In der ausgelieferten Fassung fehlte in der Verwaltung am Schreibtisch die gesamte linke Navigation – Marke oben, Fußzeile unten, dazwischen nichts. Kein Weg zu Seiten, Mediathek, Einstellungen außer über die Adresszeile.

Die Navigation steckt in einem <details>: auf dem Telefon ein Klappmenü, am Schreibtisch die stehende Spalte. Am Schreibtisch wurde der Schalter mit .shell-menu > summary { display: none } ausgeblendet – aber das Element trug kein open, und ein geschlossenes details versteckt seinen Inhalt selbst. Schalter weg und Inhalt weg.

Die Behebung. ::details-content holt den Inhalt am Schreibtisch zurück, während der Schalter ausgeblendet bleibt. Kennt ein Browser die Regel nicht, fällt über @supports die ganze Gruppe weg: dann steht der Schalter da und die Liste ist einen Klick entfernt – statt gar nicht da. Auf dem Telefon bleibt es beim Klappmenü.

Der Code dafür lag bereits im Arbeitsverzeichnis: eine parallel laufende Sitzung hatte ihn geschrieben, aber nicht committet, und damit war er in keinem Release. Übernommen ist hier nur diese Korrektur, nicht die übrige Arbeit jener Sitzung.

Wie das durchrutschen konnte – und was dagegen getan ist. Beim vorigen Bericht wurde die Navigation für in Ordnung erklärt, nachdem eine Nachbildung der Regel in einer leeren Seite geprüft worden war. Die Nachbildung enthielt die Korrektur, die ausgelieferte Datei nicht; der Befund war damit wertlos. Jetzt prüft der Browsertest die echte Verwaltungsseite: am Schreibtisch kein Schalter, Navigation sichtbar, der Eintrag „Seiten“ mit messbarer Fläche; auf 390 px Breite ein Schalter, Liste zunächst zu, ein Klick öffnet sie. Gegenprobe gemacht – gegen die ausgelieferte admin.css schlägt der Test fehl.

Geprüft. npm run test:editor vollständig, npm test 28 von 28, php vendor/bin/phpunit 452 von 452.

1.6.0: der Aufgabenplan wird veröffentlicht

Diese Fassung bringt den Aufgabenplan ohne Cronjob (ADR 073, oben beschrieben) in ein Release. Er entstand in einer parallel laufenden Sitzung und lag bis hierher unveröffentlicht im Arbeitsverzeichnis – die Releases 1.5.0 bis 1.5.5 wurden bewusst aus dem jeweiligen Tag gebaut und enthielten ihn deshalb nicht.

Vor der Veröffentlichung geprüft, nicht bloß eingepackt:

  • php vendor/bin/phpunit 452 von 452, npm test 28 von 28, npm run test:editor vollständig.
  • Die Migration 20260914000100 legt nur die Tabelle scheduled_task an und nimmt sie beim Zurückrollen wieder weg. Sie fasst keine bestehenden Daten an – das ist die risikoarme Sorte.
  • Seit einer Stunde keine Schreibzugriffe der anderen Sitzung mehr; der Stand ist ruhig.

Dabei zwei Fehler behoben:

  • config/services.yaml verwendet !tagged_iterator. Das ist gültiges Symfony, aber php bin/console lint:yaml config kennt eigene Tags nur mit --parse-tags und gab 1 zurück – der CI-Lauf in .github/workflows/ci.yml wäre daran gescheitert. Das Flag ist ergänzt.
  • src/EventSubscriber/AdminHeadersSubscriber.php (aus 1.5.3) entsprach nicht dem Stil von php-cs-fixer; composer lint gab 8 zurück. Behoben, jetzt 0 von 128 Dateien zu ändern.

Nächster konkreter Schritt

Arbeitspaket H, Teil 3b: Vorschaubilder und Suche im Werkzeugkasten, Vorlagen je Element mit Vorschaubild und das Ziehen der Spaltenbreite in der Vorschau. Danach Arbeitspaket I: banner, textbox, stack, imagebox, logo, messagebox. Reihenfolge und Begründung in docs/FLATSOME-ABGLEICH.md.

Unverändert offen: den Editor mit einem Screenreader prüfen und die übrigen Verwaltungsbildschirme in die automatische Barrierefreiheitsprüfung aufnehmen. Danach der Rest von Arbeitspaket G (Inline-Ausbau, globale Bereiche für Header und Footer, wiederverwendbare Sektionen). Danach Arbeitspaket F aus docs/AUSBAUPLAN.md: Seiten umbenennen und verschieben mit Aktualisierung aller Kindpfade und automatischer Weiterleitung, frei wählbare Startseite, geplantes Veröffentlichen, Revisionsliste mit Wiederherstellung und ein Papierkorb statt sofortigem Löschen.

kant-it.de ist seit 12.09.2026 als Pilotwebsite bestimmt. Der öffentliche Bestand wurde erhoben (WordPress 7.1, Flatsome 3.20.9 mit eigenem Child-Theme, WPForms Lite, Complianz, Google-Rezensionen, eigenes Plugin kant-nova-chat) und die noch offene Arbeit daraus in docs/AUSBAUPLAN.md in elf Arbeitspakete aufgeschlüsselt. Erhoben wurde ausschließlich die öffentlich ausgelieferte Startseite; ohne Backendzugang sind Beitragsbestand, Formularkonfiguration, Benutzer und Flatsome-Shortcodes nicht inventarisierbar. Keine externe Veröffentlichung erfolgt.

Lokale Vorschau

Öffentlich: http://127.0.0.1:8080/. Verwaltung: http://127.0.0.1:8080/admin. PHP 8.4.22 im Symfony-Produktionsmodus; der Browser wurde als umut angemeldet. Der Prozess ist sitzungsabhängig; zum erneuten Start README verwenden.