Menüs ersetzen die fest verdrahtete Navigation (Arbeitspaket A)

Migration 20260912000500 legt eine Tabelle menu an: ein Menü je Platz
(header, footer) und Sprache, Elementbaum als JSON, Version für
dieselbe Konflikterkennung wie bei Seiten.

Ein Eintrag verweist auf eine Seite, auf ein Sprungziel innerhalb
einer Seite oder auf eine Adresse (HTTPS, lokaler Pfad, mailto:,
tel:). Seitenverweise speichern die Seiten-ID, nie den Pfad - ein
Eintrag folgt damit einer umbenannten Seite. Beim Rendern fallen
Einträge zu gelöschten oder unveröffentlichten Seiten heraus, ohne
den gespeicherten Baum zu ändern und ohne die übrigen Einträge zu
verlieren.

Die Verwaltung nutzt eine nummerierte Zeilenliste statt Drag-and-drop
und funktioniert damit vollständig ohne JavaScript; Untermenüs in der
Ausgabe über details/summary.

tests/bootstrap.php verwirft var/cache/test je Lauf. Ohne Debug prüft
Symfony weder den kompilierten Container noch die Vorlagen auf
Aktualität, was zweimal ein falsches Testergebnis erzeugt hat.

Geprüft: 79 PHP-Tests / 285 Assertions unter PHP 8.2.12 und 8.4.22,
11 JavaScript-Modelltests, Lint/Format/Container, Migration zuerst
gegen eine Datenbankkopie, wiederholtes Seeding. Keine Browser- oder
Lighthouse-Läufe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 10:59:23 +02:00
1 parent 4a60adf7d7
commit de9d6e3da8
22 files changed
+765 -19

No files matched your search

+20
View File
@@ -128,3 +128,23 @@ Damit ungeprüfte Werte nie in die Ausgabe gelangen, rendert der Kern aus einem
Im Editor bleiben Plugin-Blöcke auswählbar, verschiebbar, duplizierbar und löschbar; das Eigenschaftenpanel zeigt statt Feldern einen Hinweis. In den Baustein hinein lässt sich nichts einfügen (`canContain` ist false), und `createBlock` kann keinen erzeugen – Plugin-Blöcke entstehen ausschließlich durch die jeweilige Erweiterung oder durch Import.
Rückweg: v1 bis v3 bleiben les- und schreibbar, ein Downgrade gibt es nicht. Ältere Software darf keine v4-Revision laden. Dies ist der Erhaltungsvertrag, noch kein Pluginsystem: Registrierung, Hooks und Ressourcenladen folgen in Phase 4.
## ADR 012 – Menüs als validierter Baum je Platz (12.09.2026)
Die öffentliche Navigation stand fest im Template. Der Pilot kant-it.de braucht aber Ankerziele auf der Startseite (`#dienstleistungen`, `#projekte`, `#kontakt`), externe Ziele wie `hausklar.app` sowie `tel:` und `mailto:` – eine aus der Seitenhierarchie erzeugte Navigation allein hätte dafür nicht gereicht.
Migration `20260912000500` legt eine Tabelle `menu` an: ein Menü je Platz (`header`, `footer`) und Sprache, mit Name, Elementbaum als JSON und `version` für dieselbe optimistische Konflikterkennung wie bei Seiten. Menüs sind klein und werden als Ganzes gespeichert; eine eigene Tabelle für Einträge würde Sortierung und Verschachtelung nur umständlicher machen.
Ein Eintrag verweist entweder auf eine Seite – optional zusammen mit einem Sprungziel innerhalb dieser Seite – oder auf eine Adresse. **Seitenverweise speichern die Seiten-ID, nie den Pfad.** Der Pfad entsteht erst beim Rendern. Damit folgt ein Menüeintrag einer umbenannten Seite automatisch, und eine gelöschte Seite hinterlässt keinen toten Link.
Beim Auflösen werden Einträge übersprungen, deren Seite fehlt oder unveröffentlicht ist: eine gelöschte Seite darf die Navigation nicht zerstören, und ein Entwurf darf nicht über das Menü öffentlich werden. Vorhandene Kinder eines übersprungenen Eintrags rücken nach, statt mit dem Ast zu verschwinden. Der gespeicherte Baum bleibt dabei unangetastet – wird die Seite später veröffentlicht, erscheint ihr Eintrag wieder.
Erlaubte Adressen sind HTTPS, lokale Pfade sowie `mailto:` und `tel:`; `tel:` und `mailto:` stehen hier, weil sie auf der Pilotseite echte Navigationsziele sind. `http:`, protokollrelative Adressen und `javascript:` werden abgewiesen. Sprungziele sind auf `^[a-zA-Z][a-zA-Z0-9_-]{0,64}$` begrenzt. Grenzen: 60 Einträge, zwei Ebenen.
Die Verwaltung arbeitet mit einer flachen, nummerierten Zeilenliste statt mit Drag-and-drop: Reihenfolge über die Position, Verschachtelung über die Ebene. Das funktioniert vollständig ohne JavaScript und ist mit der Tastatur bedienbar. Untermenüs in der Ausgabe nutzen `details`/`summary` und brauchen deshalb ebenfalls kein öffentliches JavaScript.
## ADR 013 – Testcache wird je Lauf verworfen (12.09.2026)
Das Testkernel läuft mit `debug = false`. Symfony prüft dann weder den kompilierten Container noch die kompilierten Twig-Vorlagen auf Aktualität. Zweimal hat das während der Entwicklung ein falsches Ergebnis erzeugt: eine geänderte Vorlage wurde aus dem Cache beantwortet und ein Test bestand fälschlich, später schlug ein geänderter Konstruktor mit einem `TypeError` aus einem veralteten Container fehl.
`tests/bootstrap.php` löscht deshalb `var/cache/test` einmal pro Lauf. Das kostet rund eine Sekunde und schließt diese Fehlerklasse aus. Zusätzlich lädt Twig in der Testumgebung Vorlagen automatisch neu, und das Login-Throttling zählt dort in einem Array-Cache statt in einem dauerhaften Pool – beide Einstellungen betreffen ausschließlich die Testumgebung, die geprüfte Drosselung selbst bleibt aktiv.
+3 -1
View File
@@ -54,10 +54,12 @@ Erhoben am 12.09.2026 ausschließlich aus der **öffentlich ausgelieferten Start
Die Reihenfolge ergibt sich aus Abhängigkeiten und aus dem Risiko, etwas später über Kundendaten ändern zu müssen. Jedes Paket endet mit einem prüfbaren Ergebnis, aktualisiertem STATUS und, wo nötig, einer Architekturentscheidung.
### A – Navigation und Menüs
### A – Navigation und Menüs — **erledigt am 12.09.2026**
*Braucht: Seitenhierarchie (erledigt).*
Umgesetzt bis auf eines: Aufklappmenüs, Anker-, Seiten- und externe Ziele, Header und Footer getrennt, `aria-current`, keine öffentliche JavaScript-Abhängigkeit, gelöschte Seiten brechen das Menü nicht. **Offen geblieben:** die Sortierung läuft über Positionsnummern statt per Drag-and-drop, und es gibt nur die beiden festen Plätze. Details in ADR 012.
Menüverwaltung mit benannten Menüs und sortierbaren Einträgen. Ein Eintrag verweist auf eine Seite, auf ein Ankerziel innerhalb einer Seite oder auf eine externe Adresse; Beschriftung ist überschreibbar. Verschachtelung für Aufklappmenüs. Header- und Footermenü getrennt. Ausgabe als semantische Navigation mit `aria-current`, ohne öffentliches JavaScript für die Grundfunktion.
Abnahme: Die feste Navigation im Template ist ersetzt. Ein Menü mit Anker-, Seiten- und externem Ziel wird korrekt ausgegeben. Eine gelöschte Seite macht das Menü nicht kaputt.
+11
View File
@@ -1,5 +1,16 @@
# Qualitätsbericht – erster ausführbarer Prototyp
## Ergänzung: Menüs, 12.09.2026
- **79 PHP-Tests / 285 Assertions** unter PHP 8.2.12 und PHP 8.4.22 erfolgreich, dazu **11 JavaScript-Modelltests**.
- Geprüft, dass die Navigation nicht mehr aus dem Template stammt: die ausgegebenen Links der Hauptnavigation entsprechen genau den gespeicherten Menüeinträgen. Seiten-, Anker- und externe Ziele einschließlich `tel:` werden korrekt gebildet, `aria-current` erscheint nur auf der tatsächlich aktuellen Seite und nicht auf einem Ankereintrag derselben Seite.
- Geprüft, dass eine gelöschte und eine noch unveröffentlichte Seite aus der Navigation fallen, ohne die übrigen Einträge zu verlieren, dass die Kinder eines übersprungenen Eintrags nachrücken und dass der gespeicherte Baum unangetastet bleibt: nach dem Veröffentlichen erscheint der Eintrag wieder. Ein Entwurf wird über das Menü nicht öffentlich.
- Zehn abgewiesene Eingaben schreiben nichts in die Datenbank: leere und überlange Beschriftung, unbekannte Zielart, fehlende Seite, Sprungziel mit Rautezeichen, Sprungziel mit führender Ziffer, `javascript:`, `http:`, protokollrelative Adresse und eine dritte Ebene. Dazu unbekannter Platz, Überschreitung der Eintragsgrenze und ein veralteter Schreibstand.
- Über HTTP geprüft: anonymer Schreibversuch führt zur Anmeldung, CSRF-Token ist Pflicht, ein veralteter Versionsstand überschreibt kein neueres Menü, und der Baum entsteht korrekt aus flachen Formularzeilen über Position und Ebene, einschließlich normalisiertem Sprungziel und ignorierter Leerzeile.
- Migration `20260912000500` zuerst gegen eine Kopie der lokalen Datenbank ausgeführt, dann nach Backup (`var/backups/before-menus-*.db`) angewendet. Wiederholtes Seeding legt fehlende Menüs an und lässt vorhandene unverändert. `composer lint`, `lint:twig`, `lint:yaml` und `lint:container` sind erfolgreich.
- **Ein Fehler in der Testumgebung wurde dabei sichtbar und behoben.** Das Testkernel läuft ohne Debug; Symfony prüft dann weder den kompilierten Container noch die Vorlagen auf Aktualität. Ein geänderter Konstruktor schlug mit einem `TypeError` aus einem veralteten Container fehl, zuvor war bereits eine Vorlagenänderung übergangen worden. `tests/bootstrap.php` verwirft `var/cache/test` jetzt einmal pro Lauf. **Damit gilt für frühere Berichte: Ergebnisse, die vor dieser Behebung entstanden sind, wurden unter einer Bedingung gemessen, die einen veralteten Cache nicht ausschloss.** Die hier berichteten Läufe entstanden nach der Behebung.
- **Auch für diesen Schritt wurden auf Wunsch des Nutzers keine Browser-, axe- und Lighthouse-Läufe ausgeführt.** Die Menüverwaltung und die Aufklappmenüs in der Ausgabe sind nicht visuell, nicht auf Barrierefreiheit und nicht auf Leistung geprüft.
## Ergänzung: Erhalt unbekannter Blöcke, 12.09.2026
- **64 PHP-Tests / 229 Assertions** unter PHP 8.2.12 und PHP 8.4.22 erfolgreich, dazu **11 JavaScript-Modelltests**.
+8 -4
View File
@@ -1,6 +1,6 @@
# Projektstatus
Stand: 12.09.2026. Arbeitstitel Kant CMS. **Seiten und Beiträge lassen sich jetzt 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.**
Stand: 12.09.2026. Arbeitstitel Kant CMS. **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.
@@ -25,16 +25,19 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt.
- 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.
- 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
- 64 PHP-Tests / 229 Assertions unter PHP 8.2.12 und 8.4.22 erfolgreich; zusätzlich 11 JavaScript-Modelltests. 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.
- 79 PHP-Tests / 285 Assertions unter PHP 8.2.12 und 8.4.22 erfolgreich; zusätzlich 11 JavaScript-Modelltests. 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.
- 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: Twig lädt Vorlagen jetzt automatisch neu. Zuvor konnte eine veraltete kompilierte Vorlage im Cache den Ausgang des Testlaufs bestimmen; der Fehler wurde beim Schreiben des Plugin-Tests sichtbar und die Behebung mit einer bewusst veränderten Vorlage gegengeprüft.
- 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 `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.
@@ -52,6 +55,7 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt.
- 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: keine Videos/PDFs/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.
- 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.
@@ -62,7 +66,7 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt.
## Nächster konkreter Schritt
Arbeitspaket A aus `docs/AUSBAUPLAN.md`: Menüverwaltung mit Seiten-, Anker- und externen Zielen, die die fest im Template stehende Navigation ersetzt. Danach Paket B (Beiträge) und C (die Blöcke, die die Pilotstartseite braucht).
Arbeitspaket B aus `docs/AUSBAUPLAN.md`: Beiträge mit Datum, Autor, Auszug und Beitragsbild, Übersichtsseite mit Paginierung, einbettbare Beitragsliste, Kategorien und Schlagwörter samt Archiven sowie RSS. Danach Paket C mit den Blöcken, die die Startseite des Piloten braucht.
`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.