Commit Graph
10 Commits
Author SHA1 Message Date
kantarciandClaude Opus 5 a40c2bb27e SEO, Weiterleitungen und strukturierte Daten (Arbeitspaket E)
Migration 20260912000800 ergänzt page.robots, page.og_media_id,
website.settings und die Tabelle redirect.

Alles Maschinenlesbare wird aus dem veröffentlichten Bestand
abgeleitet, nichts separat gepflegt: die Sitemap listet nur
veröffentlichte, indexierbare Seiten samt Beitragsübersicht und
Begriffsarchiven, und dieselbe Einstellung setzt auch das
noindex-Meta - beide Aussagen können nicht auseinanderlaufen.

Strukturierte Daten entstehen nur aus gepflegten Werten: ohne
Angaben wird kein leerer Organization-Block ausgegeben. JSON-LD ist
ein Datenblock, kein Skript, daher bleibt script-src 'none'
unberührt.

Weiterleitungen greifen vor dem 404, aber nach der Seitensuche: eine
vorhandene Seite gewinnt immer, sonst könnte ein alter Eintrag eine
neue Seite unerreichbar machen. Ketten werden abgewiesen. robots.txt
nimmt zusätzliche Zeilen nur nach festem Muster an, damit ein
Eintrag die Datei nicht umdeuten kann. llms.txt ist standardmäßig
aus.

Geprüft: 128 PHP-Tests / 507 Assertions unter PHP 8.2.12 und 8.4.22,
14 JavaScript-Modelltests, Lint/Format/Container. Dazu eine reine
Sichtprüfung im Browser auf einer lokalen Demoseite - weiterhin
keine axe- oder Lighthouse-Messung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 12:43:00 +02:00
kantarciandClaude Opus 5 d43bd91069 Formulare mit Validierung, Einwilligung und Spamschutz (Arbeitspaket D)
Migration 20260912000700 legt form und form_submission an. Der
Formularblock im Seiteninhalt enthält nur die Adresse: Felder,
Empfänger und Einwilligungstext liegen in der Datenbank, damit ein
gespeichertes Dokument nicht ändern kann, wonach gefragt wird oder
wohin eine Anfrage geht.

Serverseitige Validierung mit einer Meldung je Feld über
aria-describedby und aria-invalid; eine Ablehnung rendert die Seite
mit HTTP 422 und allen Eingaben neu, eine gültige Absendung wird mit
HTTP 303 zurückgeleitet. Alles ohne JavaScript.

Spamschutz ohne Dienst Dritter: Honeypot, signierter Zeitstempel
gegen zu schnelle und zu alte Absendungen, fünf Absendungen je
Absender und Stunde. Ein erkannter Bot bekommt dieselbe Antwort wie
ein Erfolg. Die Absenderadresse wird nie gespeichert, nur ein Hash.

Gespeichert wird vor dem Versand, damit ein kaputter Mailversand
keine Anfrage verliert. Kein CSRF-Token auf dem öffentlichen Weg,
weil das eine Sitzung und damit ein Cookie auf jeder öffentlichen
Seite bedeuten würde; Begründung in ADR 017. Dateiuploads fehlen
bewusst.

Ein echter Fehler von den Tests gefunden: das Stundenfenster des
Limits entstand aus lokaler Zeit, die Zeitstempel aber aus UTC - auf
einer Uhr vor UTC hätte das Limit nie gegriffen.

Geprüft: 117 PHP-Tests / 450 Assertions unter PHP 8.2.12 und 8.4.22,
14 JavaScript-Modelltests, Lint/Format/Container, composer audit.
Der E-Mail-Versand wurde nie wirklich ausgeführt; keine Browser- oder
Lighthouse-Läufe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:51:07 +02:00
kantarciandClaude Opus 5 c6ec00a906 Tabs als fortschreitende Verbesserung (Dokumentschema v7)
Auf Entscheidung von Umut werden Tabs jetzt mit JavaScript umgesetzt.
Die Regel "der Seiteninhalt braucht kein Browser-JavaScript" bleibt
dabei eingehalten: der Server liefert eine Ankerliste und alle Panels
mit eigenen Überschriften, versteckt nichts, und ohne Skript ist
jeder Inhalt lesbar und über die Tastatur erreichbar.

public/assets/tabs.js ergänzt tablist/tab/tabpanel, Auswahl per
Pfeiltasten, Home und Ende sowie aria-selected und wählt bei einem
passenden Sprungziel das zugehörige Panel.

Die strenge Richtlinie bleibt Voreinstellung: öffentliche Seiten
senden weiterhin script-src 'none'. Nur eine Seite mit Tab-Block
sendet script-src 'self' und bindet die eine externe Datei ein;
Inline-Skript ist nie erlaubt.

Geprüft: 109 PHP-Tests / 395 Assertions unter PHP 8.2.12 und 8.4.22,
14 JavaScript-Modelltests, Lint/Format/Container. Das Skript selbst
wurde nicht ausgeführt: Tastaturbedienung und ARIA-Verhalten sind
ohne Browserlauf ungeprüft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:41:36 +02:00
kantarciandClaude Opus 5 8600384f1a Zehn Blöcke für die Abschnitte der Pilotseite (Arbeitspaket C)
Dokumentschema v6 ergänzt iconcard, stat, quote, logos, cta, contact,
divider, spacer, accordion und accordionitem - die Abschnittsarten,
aus denen die Startseite von kant-it.de besteht.

Symbole liegen als Inline-SVG aus einer festen Liste im Template:
kein Symbolschriftsatz, keine externe Anfrage. Kontaktdaten werden zu
tel:- und mailto:-Verweisen mit bereinigter Nummer. Das Akkordeon
nutzt details/summary und braucht damit kein öffentliches
JavaScript.

Tabs sind bewusst nicht umgesetzt: ohne JavaScript gibt es dafür
keine zugängliche Lösung, und eine CSS-Variante über Radiobuttons
wird als Auswahlfeld statt als Tabliste angekündigt. Begründung in
ADR 015.

Zwei Fehler von den Tests gefunden und behoben: die Regel "ein
Akkordeon-Eintrag gehört in ein Akkordeon" griff nicht auf oberster
Dokumentebene, und ein JavaScript-Test suchte im vorherigen
Dokumentstand, weil history.document vor history.change ausgewertet
wurde.

Geprüft: 107 PHP-Tests / 380 Assertions unter PHP 8.2.12 und 8.4.22,
13 JavaScript-Modelltests, Lint/Format/Container. Keine Browser- oder
Lighthouse-Läufe; für die neuen Blöcke liegt damit keine visuelle
Kontrolle und keine Barrierefreiheitsprüfung vor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:34:48 +02:00
kantarciandClaude Opus 5 ebd5883593 Beiträge mit Archiven, Beitragsliste und Feed (Arbeitspaket B)
Migration 20260912000600 ergänzt page.excerpt, author,
featured_media_id und published_at sowie die Tabellen term und
page_term. Beitragsmetadaten beschreiben die Seite, nicht ihren
Inhalt, und liegen deshalb als Spalten statt im Blockdokument: das
Archiv sortiert und paginiert damit in SQL, statt für jede
Auflistung alle Dokumente zu dekodieren.

Kategorien und Schlagwörter teilen sich eine Tabelle mit einem
kind-Feld. Beim Löschen einer Seite verschwinden ihre Zuordnungen,
die Begriffe bleiben als wiederverwendbare Etiketten; ein leer
gewordenes Archiv antwortet weiter mit 200 und leerer Liste.

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. Archive unter /beitraege, /kategorie/{slug} und
/schlagwort/{slug}, Feed unter /feed.xml; alle vier Segmente sind
für Wurzelseiten reserviert.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:25:09 +02:00
kantarciandClaude Opus 5 de9d6e3da8 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>
2026-09-12 10:59:23 +02:00
kantarciandClaude Opus 5 4a60adf7d7 Ausbauplan bis zum Pilotbetrieb, verankert an kant-it.de
kant-it.de ist als Pilotwebsite bestimmt. Der öffentlich ausgelieferte
Bestand wurde erhoben: WordPress 7.1, Flatsome 3.20.9 mit eigenem
Child-Theme, WPForms Lite, Complianz, Google-Rezensionen und das
eigene Plugin kant-nova-chat. Die Startseite ist ein Onepager mit
Ankernavigation, Beiträge liegen flach unter der Wurzel.

Daraus elf Arbeitspakete mit Abhängigkeiten und Abnahme, von der
Menüverwaltung bis zur Migration. Festgehalten ist auch, was ohne
Backendzugang und ohne den Quellcode des Chat-Plugins nicht
inventarisierbar und damit nicht schätzbar ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:48:56 +02:00
kantarciandClaude Opus 5 8e0006f883 Unbekannte Blöcke erhalten statt abweisen (Dokumentschema v4)
Der Lesevalidator wies bisher jeden unbekannten Blocktyp ab, womit der
Fallback im Renderer unerreichbar war: eine Seite mit Bausteinen aus
einer deaktivierten Erweiterung wäre nicht mehr ladbar gewesen. Ab
Schema v4 gilt ein Typ mit Namensraum (acme.slider) als Baustein einer
Erweiterung und bleibt erhalten. Der Kern prüft weiterhin ID, Tiefe,
Blockanzahl und Dokumentgröße, aber bewusst nicht die Props - er kennt
das Schema der Erweiterung nicht.

Damit ungeprüfte Werte nie ausgegeben werden, rendert der Kern aus
einem solchen Block nichts: öffentlich nur ein HTML-Kommentar, in der
Editorvorschau ein Platzhalter. Im Editor bleiben die Blöcke
auswählbar, verschiebbar, duplizierbar und löschbar, aber nicht
bearbeitbar, und es lässt sich nichts in sie einfügen.

Testumgebung: Twig lädt Vorlagen jetzt automatisch neu. Ohne Debug
konnte eine kompilierte Vorlage im Cache den Ausgang eines Testlaufs
bestimmen.

Geprüft: 64 PHP-Tests / 229 Assertions unter PHP 8.2.12 und 8.4.22,
11 JavaScript-Modelltests, Lint/Format/Container. Keine Browser- oder
Lighthouse-Läufe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:37:30 +02:00
kantarciandClaude Opus 5 a6ffb9bdfd Seiten im Browser anlegen und löschen, Adresshierarchie und Beitragstyp
Migration 20260912000400 ergänzt page.path, parent_id, type und
created_at. Die websiteweite Eindeutigkeit wandert vom Slug auf den
vollständigen Pfad, damit gleiche Segmente unter verschiedenen Eltern
möglich sind. Nur ADD COLUMN: Index- oder Fremdschlüsseländerungen
würden SQLite die Tabelle neu aufbauen lassen, was am Fremdschlüssel
revision.page_id scheitert; die Indizes werden in postUp() gesetzt.

POST /admin/pages legt Seiten und Beiträge als privaten Entwurf an
(ROLE_EDITOR), POST /admin/pages/{id}/delete entfernt eine Seite mit
allen Revisionen (ROLE_ADMIN). Beides mit CSRF, ohne JavaScript
bedienbar, mit Schutz für Elternseiten und veraltete Versionen.
Die öffentliche Route bedient verschachtelte Pfade und lässt die
CMS-Routen unberührt.

Testumgebung: Login-Throttling zählt jetzt in einem Array-Cache,
weil die persistierten Zähler die Anmeldung über Testläufe hinweg
aussperrten.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:31:46 +02:00
kantarciandClaude Opus 5 82b07820bc Kant CMS: Grundgerüst, visueller Editor, Mediathek
Symfony 7.4 / Twig / Doctrine DBAL auf herkömmlichem PHP-Webhosting.
Versionierte Blockdokumente (v1-v3) mit unveränderlichen Revisionen,
getrennten Entwürfen/Veröffentlichungen und optimistischer
Konflikterkennung. Visueller Editor mit Strukturbaum, Live-Vorschau,
responsiven Gerätewerten, Abschnittsvorlagen, Bildmediathek,
Galerien und Effekten. Öffentliche Ausgabe ohne Editor-JavaScript.

Geprüft: 41 PHP-Tests / 155 Assertions, 10 JavaScript-Modelltests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:19:05 +02:00