Files
KantSites/docs/AUSBAUPLAN.md
T
kantarciandClaude Opus 5 af3589c95c
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s
Seitenbreite wirkt, vier Menueplaetze, Seiten ohne Kopfbereich
Drei Fragen, drei unterschiedliche Antworten.

Die globale Seitenbreite war einstellbar und wirkte nur in eine Richtung:
site.css schrieb width:min(1180px,...), also schluckte die 1180 jeden
groesseren Wert aus dem Designbereich. Schmaler ging, breiter nicht, ohne
jede Meldung. Die Zahl kommt jetzt aus dem Theme. Das ist der dritte Fehler
dieser Art an einem Tag - nach der Rahmenstaerke und der Bildrundung -, und
alle drei hatten dieselbe Ursache: CSS, das nie gegen eine Erwartung geprueft
wurde.

Menueplaetze und Menueart waren vermischt. Wo ein Menue steht, bestimmt der
Block und war immer frei. Welche Eintraege es zeigt, bestimmt der Platz -
davon gab es zwei, jetzt vier (Haupt, zweite, Fusszeile, Rechtliches). Wie es
aussieht, war gar nicht einstellbar: jetzt waagerecht, untereinander oder
aufklappbar als Burger. Der Burger ist ein details-Element, denn ein Menue,
das ein Skript zum Oeffnen braucht, geht auf einer Seite ohne Skript nicht auf.

"Seiten ohne Menue" war nicht moeglich - der Kopfbereich war ein Dokument fuer
alle Seiten. Jetzt waehlt jede Seite: Standard, ein anderer Bereich, oder
keiner. Dafuer musste die Regel "es gibt genau einen Kopf- und Fussbereich"
fallen; sie war richtig, solange niemand waehlen konnte, und wurde falsch,
sobald man waehlen kann. Ein bestehender Test, der diese Regel zusicherte, ist
deshalb umgeschrieben und prueft jetzt die neue.

Gespeichert wird die Wahl, nicht das Ergebnis: leer heisst Standard, none
heisst keiner, sonst die Adresse. Ein Verweis auf einen geloeschten Bereich
faellt auf den Standard zurueck, statt die Seite stillschweigend ohne
Navigation auszuliefern.

Neu im Ausbauplan: ein einzelnes install.php, das das Releasearchiv
herunterlaedt und entpackt, damit niemand tausende Dateien per FTP hochladen
muss. Mit den Regeln, die darueber entscheiden, ob das eine Hilfe oder eine
Einladung ist: Adresse und SHA-256 stehen in der Datei und nicht im Formular,
HTTPS mit Zertifikatspruefung, Schutz gegen ZIP-Slip, Selbstloeschung danach.

336 PHP-Tests / 1354 Assertions, 17 JS-Modelltests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 21:15:03 +02:00

31 KiB
Raw Blame History

Ausbauplan bis zum Pilotbetrieb

Stand: 12.09.2026. Ergänzt ENTWICKLUNGSPLAN.md um die Aufschlüsselung der noch offenen Arbeit. Der Phasenplan bleibt die übergeordnete Reihenfolge; dieses Dokument benennt die konkreten Arbeitspakete, ihre Abhängigkeiten und ihre Abnahme. Der aktuelle Stand steht in docs/STATUS.md, begründete Entscheidungen in docs/ARCHITECTURE.md.

Pilotwebsite: kant-it.de

Umut hat kant-it.de am 12.09.2026 als Pilotwebsite bestimmt. Der Ausbau richtet sich ab jetzt an dieser Website aus, nicht an einem generischen CMS-Funktionsumfang.

Erhobener Bestand

Erhoben am 12.09.2026 ausschließlich aus der öffentlich ausgelieferten Startseite. Es bestand kein Zugriff auf das WordPress-Backend. Alles Folgende ist damit belegt, aber nicht vollständig: Entwürfe, nicht verlinkte Seiten, Formularkonfigurationen, Benutzerrollen, individuelle Felder und der tatsächliche Beitragsbestand sind von außen nicht sichtbar.

Technischer Bestand

Bereich Befund
Kern WordPress 7.1
Theme Flatsome 3.20.9 mit Child-Theme kant-it-flatsome-ux-child, eigenes kant-it-home.js
Startseite Seitenvorlage page-blank, vollständig im UX Builder aufgebaut
Formulare WPForms Lite
Cookie-Banner Complianz GDPR
Bewertungen WP Google Places Review Slider (externe Google-Rezensionen)
Chat kant-nova-chat – ein eigenes Plugin, kein Marktplatzprodukt
Suche Flatsome Live Search

Inhaltlicher Bestand

  • Startseite als Onepager. Die Hauptnavigation verweist auf Ankerziele derselben Seite (#dienstleistungen, #projekte, #kontakt), nicht auf Unterseiten.
  • Beiträge mit flachem Permalink direkt unter der Wurzel, zum Beispiel /kanttresor-dsgvo-konforme-archivierung-fuer-moderne-unternehmen/ und /neue-webseite-fuer-klinik-umsetzer-de-umgesetzt/. Dazu eine Übersichtsseite /productsolutions.
  • Rechtsseiten /impressum/ und /datenschutzerklaerung/.
  • Externe Ziele: hausklar.app, kanttresor.kant-it.de, TeamViewer-QuickSupport-Download.
  • Kontaktdaten als Inhalt: Telefon, E-Mail, Anschrift in Baden-Baden, jeweils als tel:- und mailto:-Verweis.

Abschnittsarten der Startseite, die als Blöcke gebraucht werden: Hero mit Handlungsaufruf, Logoleiste, Kennzahlen, Leistungsraster mit Symbol und Text, Lösungs- und Projektkarten mit Bild, Kundenstimmen mit Bewertung und Person, Hinweisbox mit Download, Handlungsaufruf-Band, Kontaktbereich aus Kontaktdaten und Formular.

Was daraus folgt

  1. Menüs müssen Ankerziele und externe Ziele können, nicht nur Seitenverweise. Ein reiner Seitenbaum als Navigation reicht für diesen Piloten nicht.
  2. Beiträge liegen flach unter der Wurzel und haben eine eigene Übersichtsseite. Das passt zum heutigen Datenmodell (type = post, path ohne Elternteil) und braucht keine Schemaänderung.
  3. Das Formular ist der Kern der Seite. Ohne serverseitig validiertes Kontaktformular mit Einwilligung und Versand ist der Pilot nicht livefähig.
  4. Drei Fremdbausteine sind keine Inhalte, sondern Integrationen: Cookie-Banner, Google-Rezensionen und der eigene Chat. Sie gehören hinter die Plugin-Schnittstelle, nicht in den Kern. Der Erhaltungsvertrag für unbekannte Blöcke (ADR 011) ist die Grundlage dafür.
  5. kant-nova-chat ist Eigenentwicklung. Für die Migration muss sein Quellcode vorliegen; ohne ihn ist der Chat kein migrierbares Feature, sondern ein Neubau.
  6. Strukturierte Daten lohnen sich hier konkret: Anschrift, Telefon und Öffnungszeiten eines regionalen Dienstleisters sind ein echter LocalBusiness-Fall, keine Pflichtübung.

Offen und nur von Umut zu beantworten

  • Zugang zum WordPress-Backend oder ein Export, um Beiträge, Formularfelder, Benutzer und Flatsome-Shortcodes tatsächlich zu inventarisieren.
  • Quellcode von kant-nova-chat.
  • Hostinganbieter, Tarif, PHP- und Datenbankversion, Dokumentenstamm, CLI- und Deploymentzugang.
  • Umgang mit den Google-Rezensionen: weiter über einen externen Dienst oder als gepflegter Inhalt.
  • Soll der Pilot beim Cookie-Banner bleiben, und wenn ja, mit welchem Dienst.

Arbeitspakete

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 — 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.

B – Beiträge — erledigt am 12.09.2026

Braucht: A für die Verlinkung.

Umgesetzt: Datum, Autor, Auszug und Beitragsbild, Übersicht mit Paginierung, Beitragsliste als Block, Einzelansicht mit Datum, Autor und Begriffen, Kategorien und Schlagwörter mit Archiven, RSS. Offen geblieben: keine Bildvorschau in der Verwaltung (das Beitragsbild wird über seine Mediathek-ID eingetragen), keine Begriffsverwaltung zum Umbenennen oder Zusammenführen, feste zehn Beiträge je Archivseite. Details in ADR 014.

Beitragsdatum, Autor, Auszug und Beitragsbild im Datenmodell. Übersichtsseite mit Sortierung nach Datum und Paginierung. Ein Block „Beitragsliste“ zum Einbetten in beliebige Seiten, mit Anzahl und Filter. Einzelansicht mit Datum und Autor. Kategorien und Schlagwörter einschließlich ihrer Archivseiten. RSS-Feed.

Abnahme: Die vier bestehenden Projektbeiträge des Piloten lassen sich als Beiträge abbilden, erscheinen in der Übersicht und in einer eingebetteten Liste auf der Startseite.

C – Blöcke für den Piloten — erledigt am 12.09.2026

Braucht: nichts; parallel möglich.

Umgesetzt: Symbolkarte, Kennzahl, Kundenstimme, Logoleiste, Aufruf-Band, Kontaktdaten, Trennlinie, Abstand und Akkordeon, alle ohne neue öffentliche JavaScript-Abhängigkeit. Tabs wurden zunächst zurückgestellt (ADR 015) und auf Entscheidung von Umut anschließend mit JavaScript umgesetzt: der Server liefert vollständigen Inhalt, ein Skript macht daraus ein Tab-Widget, und die strenge Skriptrichtlinie bleibt überall sonst bestehen (ADR 016). Offen: das Skript ist nicht im Browser geprüft.

Die oben erhobenen Abschnittsarten als Blöcke: Symbol-und-Text-Karte, Kennzahl, Kundenstimme mit Bewertung, Logoleiste, Handlungsaufruf-Band, Kontaktdatenblock, Trennlinie und Abstandhalter. Dazu Akkordeon und Tabs, weil beide im Plan stehen und ohne JavaScript über details/summary beziehungsweise Sprungmarken lösbar sind.

Abnahme: Die Startseite des Piloten ist ohne Code nachbaubar. Keine neue öffentliche JavaScript-Abhängigkeit.

D – Formulare — erledigt am 12.09.2026, ohne Dateiuploads

Braucht: nichts; das größte eigenständige Paket.

Umgesetzt: Feldtypen Text, E-Mail, Telefon, Mehrzeiliges, Auswahl und Einwilligung, serverseitige Validierung mit Meldung je Feld, Einwilligungszeitpunkt, E-Mail-Versand mit Antwortadresse, gespeicherte Einsendungen in der Verwaltung und Spamschutz aus Honeypot, signiertem Zeitstempel und Rate-Limit. Offen geblieben: Dateiuploads (eigener nicht öffentlicher Ablageort und Typprüfung, Begründung in ADR 017), Löschen und Umbenennen von Formularen, Export und Aufbewahrungsfrist für Einsendungen. Der tatsächliche Versand ist ungeprüft und gehört zu Paket J.

Formularbaukasten mit Feldtypen Text, E-Mail, Telefon, Auswahl, Mehrzeiliges, Einwilligungskästchen und Datei. Pflichtfelder, serverseitige Validierung mit Fehlermeldungen am Feld, Beschriftungen und Fehlerauszeichnung für Screenreader. Versand per E-Mail mit Antwortadresse, dazu Speicherung der Einsendungen mit Ansicht in der Verwaltung. Spam-Schutz ohne Drittanbieter-CAPTCHA: Honeypot, Zeitschwelle, Rate-Limit pro IP. Einwilligungstext mit Verweis auf die Datenschutzerklärung, Zeitpunkt der Einwilligung wird mitgespeichert.

Abnahme: Das Kontaktformular des Piloten ist nachgebaut, funktioniert ohne JavaScript, weist ungültige Eingaben serverseitig ab und stellt eine E-Mail zu. Ein abgewiesener Versand verliert keine Eingaben.

E – SEO und maschinelle Zugänglichkeit — erledigt am 12.09.2026

Braucht: A und B für Sitemap und Breadcrumbs.

Umgesetzt: Indexierungssteuerung und Vorschaubild je Seite, Sitemap, robots.txt, Weiterleitungen mit 301 und 410, strukturierte Daten für Organization/LocalBusiness, Article und BreadcrumbList, Brotkrumen-Block und optionale llms.txt. Offen geblieben: getrennte SEO-Titel, Auflösung des Vorschaubilds zu og:image, Weiterleitungen mit Mustern, und automatische Weiterleitung beim Umbenennen (gehört zu Paket F). Details in ADR 018.

SEO-Felder je Seite: Titel, Beschreibung, Indexierungssteuerung, Open-Graph-Bild. Automatische sitemap.xml, robots.txt, Weiterleitungsverwaltung mit HTTP 301 und 410. Strukturierte Daten für Organization/LocalBusiness aus gepflegten Unternehmensdaten, für Article aus Beiträgen, für BreadcrumbList aus der Hierarchie. Breadcrumb-Block. llms.txt optional und abschaltbar, ohne jede Behauptung über Ranking oder Zitierung.

Abnahme: Sitemap und robots.txt stimmen mit dem veröffentlichten Bestand überein. Eine Adressänderung erzeugt eine Weiterleitung. Strukturierte Daten decken sich mit sichtbarem Inhalt und bestehen die Prüfung mit dem Testwerkzeug.

F – Seiten verwalten — erledigt am 12.09.2026

Braucht: A und E für Menüs und Weiterleitungen.

Umgesetzt: Umbenennen und Verschieben mit Aktualisierung aller Kindpfade und automatischer Weiterleitung, geplante Veröffentlichung, Fassungsverlauf mit Wiederherstellen und ein Papierkorb statt sofortigem Löschen. Die frei wählbare Startseite ist ebenfalls umgesetzt (ADR 023). Offen geblieben: kein automatisches Leeren des Papierkorbs und kein Vergleich zweier Fassungen.

Seiten umbenennen und verschieben, mit Aktualisierung aller Kindpfade in einer Transaktion und automatischer Weiterleitung von der alten Adresse. Startseite frei wählbar. Geplantes Veröffentlichen. Revisionsliste mit Vergleich und Wiederherstellung in der Oberfläche. Papierkorb statt sofortigem Löschen.

Abnahme: Eine Seite mit Unterseiten wird verschoben; alle alten Adressen leiten weiter, Menüs bleiben intakt.

G3 – Designer auf Flatsome-Niveau (Nutzerauftrag vom 12.09.2026) — weitgehend erledigt am 12.09.2026 (ADR 036)

Umuts Auftrag: „Prüfe es und baue alles wirklich alles genauso ein styles, effekte, und mehr.“ Der Vergleich steht in docs/DESIGNER-VERGLEICH.md. Gebaut: 93 statt 50 Eigenschaften, neun Einblendungen mit Verzögerung, sieben Hover-Effekte, Bild-Eigenschaften mit eigenem Ziel, Positionierung, Filter, Symbol, Fortschritt, Slider, Zeilenvorlagen.

Offen und dort begründet: Sichtbarkeitsschalter je Gerät, Parallax, Video-Hintergrund, selbst gehostetes Video, Countdown, Popup, Einblendung beim Scrollen, soziale Symbole, Preistabelle, Teammitglied, Suche, Vorlagenbibliothek. Die vier mit Skriptbedarf (Countdown, Popup, Scroll-Einblendung, Autoplay) sind eine Entscheidung, keine Lücke – sie weichen script-src 'none' auf und werden nur auf Zuruf gebaut.

G – Editor für die tägliche Arbeit

Braucht: C.

Inline-Textbearbeitung direkt in der Vorschau. Globale Farben und Schriften. Header und Footer als bearbeitbare Bereiche. Wiederverwendbare Sektionen mit Unterscheidung zwischen Kopie und globaler Verknüpfung. Autosave mit sichtbarem Zustand. Bildzuschnitt in der Mediathek, Ordner, Paginierung und Unterstützung für PDF und Video.

Abnahme: Die Pilotwebsite lässt sich vollständig ohne Code pflegen. Eine geänderte globale Farbe wirkt auf allen Seiten.

G2 – Freie Bereiche, Rich Text und eigener Code (Nutzerauftrag vom 12.09.2026)

Umut hat den Umfang von G erweitert. Vier Punkte, in dieser Reihenfolge:

  1. Kopf- und Fußbereich frei aus Elementen bauen — erledigt am 12.09.2026 (ADR 026). Ursprünglicher Wortlaut: Kopf- und Fußbereich frei aus Elementen bauen, genau wie eine Sektion – keine feste Marke, keine feste Navigation im Template. Dazu wiederverwendbare Sektionen, die verknüpft und nicht kopiert werden: eine Änderung erreicht jede Seite, die sie nutzt.
  2. Rich Text in Textelementen — erledigt am 12.09.2026 (ADR 027). Umgesetzt für Textblöcke mit Werkzeugleiste und HTML-Ansicht; gefiltert wird auf dem Server beim Speichern. Überschrift, Button und Zitat bleiben bewusst Klartext.
  3. Eigenes CSS und eigenes Skript je Element. — erledigt am 12.09.2026 (ADR 028). Ursprünglicher Wortlaut: Eigenes CSS und eigenes Skript je Element. Eigenes CSS ist unkritisch und für alle mit Editorrolle nutzbar. Eigenes Skript ist es nicht: es ist gespeichertes, ausführbares JavaScript auf der Website. Wer es setzen darf, kann Code im Browser jedes Administrators ausführen. Deshalb: nur für Administratoren, Ausgabe über eine seiteneigene Skriptdatei statt inline, keine Ausführung in der Editorvorschau, und script-src 'self' nur auf Seiten, die tatsächlich ein Skript tragen. Der Preis steht in der Oberfläche.
  4. Verwaltung neu gestalten — erledigt am 12.09.2026 (ADR 029). Der Editor behält seinen Aufbau und übernimmt die neue Gestaltung; neu gebaut sind der Rahmen, die Navigation, die Seitenliste und die Formularseiten. Ursprünglicher Wortlaut: Verwaltung neu gestalten – einfacher, schöner, ohne Nachdenken bedienbar. Gestaltungsfreiheit ausdrücklich bei mir; der bisherige Stil ist nicht bindend. Das betrifft Navigation zwischen den Bereichen, die Seitenliste, die Panels im Editor und die Formularseiten, die derzeit alle als lange Formulartabellen gebaut sind.

Abnahme: Kopf- und Fußbereich sind im Editor gestaltbar und öffentlich sichtbar. Eine verknüpfte Sektion auf zwei Seiten ändert sich an beiden Stellen gleichzeitig. Ein Textelement lässt sich formatiert und als HTML bearbeiten, und keine Auszeichnung außerhalb der Positivliste übersteht das Speichern. Eigenes CSS wirkt, eigenes Skript wirkt nur für Administratoren gesetzt und nur auf der veröffentlichten Seite. Die Verwaltung ist ohne Anleitung bedienbar.

H – Plugins und Templates

Braucht: den Erhaltungsvertrag (erledigt).

Registrierung von Blöcken, Einstellungen und Hooks über dokumentierte Schnittstellen. Manifest mit Versionsanforderungen, Abhängigkeitsprüfung, Aktivieren und Deaktivieren ohne Inhaltsverlust. Ressourcen werden nur dort geladen, wo der Block vorkommt. Templatepaket mit Designwerten und Seitenvorlagen; zwei Templates auf denselben Inhalten erprobt. Als Referenzplugin der Chatbaustein des Piloten.

Abnahme: Templatewechsel erhält die Inhalte. Das Referenzplugin lässt sich aktivieren, aktualisieren und deaktivieren; seine Blöcke werden bei Deaktivierung kontrolliert dargestellt statt gelöscht.

I – Benutzer, Betrieb und Leistung

Braucht: nichts; teils vorziehbar.

Benutzerverwaltung und Passwortänderung in der Oberfläche. — erledigt am 12.09.2026 (ADR 030). Offen bleibt der Rest des Pakets: Inhaltscache mit gezielter Erneuerung beim Veröffentlichen. Bildformate und Ressourcenpakete prüfen. Sicherung und Wiederherstellung erproben — als eigenes Arbeitspaket O geführt, weil Umut daraus eine Funktion haben will und nicht eine Übung. Updateverfahren mit Datenbankmigration und Rückfallstrategie dokumentieren — geplant am 12.09.2026 in docs/UPDATE.md: neue Version über einen Git-Tag ziehen, mit Vorprüfung, Sicherung, Wartungszustand, Migration, Rauchtest und Rückweg. Umsetzung offen; zwei Entscheidungen hängen am Hosting (Composer auf dem Server oder vendor/ im Tag, und ob es Shell-Zugriff gibt).

Abnahme: Veröffentlichen macht die Änderung sofort sichtbar und erneuert nur die betroffenen Einträge. Eine Wiederherstellung aus dem Backup ist nachgewiesen.

J – Hosting und MySQL

Braucht: die Antwort von Umut.

Anbieter und Tarif festlegen. MySQL oder MariaDB tatsächlich nachweisen statt nur vorbereiten. Deployment, Dokumentenstamm, Dateirechte, Cronjobs und E-Mail-Versand einrichten. Staging aufsetzen. Erst danach sind Leistungsmessungen aussagekräftig: die bisherigen Lighthouse-Werte stammen von einem lokalen PHP-Server mit kleinen Testinhalten und sagen über das Zielhosting nichts.

Abnahme: Die Anwendung läuft auf dem Zielhosting mit MySQL. Messungen mit realen Inhalten liegen vor, einschließlich der Bedingungen.

L – Statistik (Nutzerauftrag vom 12.09.2026) — erledigt am 12.09.2026 (ADR 034)

Braucht: nichts.

Umut: „Als todo noch statistik kannst du das einbauen. Wieviel wann wo wie die seite benutzt haben?“ Ja – und zwar so, dass sie keine Einwilligung braucht, denn eine Messung, die niemanden wiedererkennt, ist keine Verarbeitung, über die zu fragen wäre. Das ist der Unterschied zu Google Analytics und der Grund, warum das Banner davon unberührt bleibt.

Erhoben wird serverseitig, beim Ausliefern der Seite: Tag und Stunde (UTC), Pfad, Antwortstatus, ob es ein Erstaufruf oder eine Folgeseite war (aus dem Referrer derselben Website), Herkunftsdomain bei externem Referrer, Gerätegröße-Klasse aus keinem Cookie sondern aus nichts – das geht nicht, also entfällt „wo auf der Seite geklickt wurde“ ersatzlos, solange kein Skript läuft.

Gespeichert wird kein Datensatz je Aufruf, sondern ein Zähler: eine Tabelle stat_hit mit (day, hour, path, status, referrer_host) und count, hochgezählt per INSERT ... ON CONFLICT DO UPDATE. Keine IP-Adresse, kein Kennzeichen, kein Cookie, keine Sitzung – auch nicht gehasht, denn ein Hash einer IP-Adresse bleibt ein Personenbezug. Dadurch wächst die Tabelle mit der Zahl der Seiten, nicht mit der Zahl der Besuche.

Was das beantwortet: wie viele Aufrufe wann, welche Seiten, woher verlinkt, welche Adressen 404 liefern, wie sich das über Wochen entwickelt. Was das nicht beantwortet, und zwar nie: Besucher gegen Aufrufe (ohne Wiedererkennung nicht unterscheidbar), Verweildauer, Absprungrate, Klickpfade einzelner Personen, Land oder Stadt. Wer das braucht, braucht Wiedererkennung, und damit eine Einwilligung – dann wäre es ein eigener Baustein in der Gruppe statistics, die dafür schon existiert.

Umsetzung: ein Abonnent, der nach der Antwort zählt (nur text/html, nur Status 200/404, Verwaltung und Assets ausgenommen, Bots per User-Agent-Muster verworfen), ein Bildschirm „Statistik“ mit Tages- und Wochensicht als serverseitig gezeichnetes SVG-Balkendiagramm ohne JavaScript, Aufbewahrung 14 Monate mit Verdichtung auf Tageswerte nach 60 Tagen, und ein Export als CSV. Abnahme: ein Aufruf erhöht genau einen Zähler; ein Aufruf der Verwaltung erhöht keinen; die Tabelle enthält nach 1000 Aufrufen keine Spalte, aus der eine Person hervorgeht. Alle drei erfüllt. Umgesetzt ist zusätzlich der CSV-Export und cms:stats:prune für Verdichtung und Löschung. Abweichung vom Plan: die Gerätegrößen-Klasse entfällt, weil sie ohne Skript nicht feststellbar ist – das stand schon im Plan und ist so geblieben.

M – Inhalte und Ladeverhalten prüfen (Nutzerauftrag vom 12.09.2026)

Braucht: nichts.

Umut: „alle inhalte prüfen immer pagination und minimal laden“. Als durchgehende Regel, nicht als einmalige Aufräumaktion:

  • Jede Liste blättert. Beitragsarchiv (zehn je Seite) und Mediathek (24 je Seite) tun es. Zu prüfen und zu ändern: Seitenübersicht, Fassungsliste (feste Obergrenze 30 statt Blättern), Formulareinsendungen (feste Obergrenze 50), Benutzerliste, Weiterleitungen, Menüeinträge. Eine feste Obergrenze ist kein Blättern: sie verschweigt, dass etwas fehlt.
  • Bilder minimal. loading="lazy" und width/height überall, srcset mit den vorhandenen Breiten statt einer Größe für alle, und kein Bild über der Falz mit lazy (das verzögert das Wichtigste).
  • Ein Ladebudget je Seite, in der Browserprüfung nachgemessen statt geschätzt: HTML, CSS und Bilder je Seite mit einer Obergrenze, die bricht, wenn sie überschritten wird. Heute lädt eine öffentliche Seite kein JavaScript, zwei bis drei Stylesheets und die Bilder – das soll so bleiben und nachweisbar sein.
  • Abfragen je Seite zählen. Eine Seite mit zehn verknüpften Sektionen darf nicht zehn Einzelabfragen auslösen. Messen, dann zusammenfassen.

Abnahme: keine Liste in der Verwaltung, die mehr Einträge hat als sie zeigt, ohne das zu sagen und ohne Blättern. Ein automatischer Test, der die Größe der Startseite und die Anzahl der Datenbankabfragen prüft.

N – Installer (Nutzerauftrag vom 12.09.2026)

Teilweise erledigt am 12.09.2026: der Updater ist gebaut (ADR 035), der Installer nicht. Beide teilen die Systemprüfung, die jetzt existiert und dort wiederverwendet wird.

Braucht: die Hostingentscheidung aus Arbeitspaket J für den Schritt „Datenbank“.

Umut: „Einen Installer brauche ich noch wie wordpress schritt für schritt alles einrichten migration ausführen usw.“ Heute braucht eine Installation bin/setup.php und Konsolenzugriff – auf geteiltem Hosting ist das genau die Hürde, an der es scheitert.

Ablauf, wie bei WordPress, aber ohne dessen Nachlässigkeiten: eine Seite unter /install, die nur antwortet, solange keine Installation existiert, und danach dauerhaft 404 liefert. Schritte: (1) Systemprüfung – PHP-Version, benötigte Erweiterungen, Schreibrechte auf var/ und den Medienordner, ob public/ wirklich der Dokumentenstamm ist; jede Prüfung mit dem Satz, was zu tun ist, wenn sie fehlschlägt. (2) Datenbank – SQLite-Datei oder MySQL-Zugangsdaten, Verbindung sofort geprüft statt erst beim Speichern. (3) Migration ausführen, mit Ausgabe der angewandten Versionen. (4) Erstes Administratorkonto – Name und Passwort, dieselben Regeln wie in der Benutzerverwaltung. (5) Website – Name, Adresse, Sprache; dazu Kopf- und Fußbereich, Startseite sowie Datenschutz- und Impressumsgerüst anlegen. (6) Fertig: Anmeldung, und eine Liste dessen, was noch auszufüllen ist.

Davor steht ein einzelner Dateidownload (Nutzerauftrag vom 12.09.2026: „einen installer das eine php ist und alle dateien herunterladet dann muss man nicht alles hochladen. Am besten zip herunterladen dann entpacken“). Eine Datei install.php per FTP hochladen, im Browser aufrufen, fertig – statt tausende Dateien einzeln zu übertragen. Das ist der Teil, der über Sicherheit entscheidet, deshalb die Regeln vorweg:

  • Die Adresse steht in der Datei, nicht im Formular. Ein Installer, der eine URL abfragt und den Inhalt entpackt, ist eine Einladung, fremden Code auf den Server zu legen. Geladen wird von einer festen Adresse des Releasearchivs.
  • Die Prüfsumme steht ebenfalls in der Datei. SHA-256 des Archivs, in install.php eingebacken und vor dem Entpacken verglichen. Stimmt sie nicht, wird abgebrochen – ohne Prüfsumme kein Entpacken, auch nicht mit Häkchen.
  • HTTPS mit Zertifikatsprüfung, kein verify_peer => false, kein Rückfall auf HTTP.
  • Schutz gegen ZIP-Slip: jeder Eintrag wird gegen das Zielverzeichnis geprüft, bevor er geschrieben wird. Ein Archiveintrag ../../.env darf nichts außerhalb des Ziels anfassen.
  • Nach dem Entpacken löscht sich die Datei selbst und verweigert jeden weiteren Aufruf, sobald eine Installation existiert.
  • Vorprüfung zuerst: Schreibrechte, ZipArchive oder PharData, genug Platz, PHP-Version, und ob der Download überhaupt möglich ist (allow_url_fopen oder curl). Fehlt etwas, sagt die Seite welche Zeile in welcher Datei zu ändern ist – statt auf halbem Weg zu scheitern.
  • vendor/ ist im Archiv. Damit ist die offene Frage aus Arbeitspaket J für diesen Weg entschieden: das Releasearchiv wird auf einem Build-Rechner erzeugt und bringt die Abhängigkeiten mit, weil geteiltes Hosting kein Composer hat.

Danach übernimmt der eigentliche Installer unten: Systemprüfung, Datenbank, Migration, erstes Konto, Website.

Die drei Punkte, an denen WordPress-Installer regelmäßig zum Problem werden, und was hier anders läuft:

  • Der Installer schreibt .env, braucht dafür Schreibrechte im Projektstamm und sagt es, statt die Datei stillschweigend nicht zu schreiben. APP_SECRET wird erzeugt, nicht abgefragt.
  • Er ist nach dem ersten Durchlauf tot. Gesperrt wird über den Zustand der Datenbank (existiert ein Konto?), nicht über eine Datei, die jemand löschen kann.
  • Er legt keine Zugangsdaten in ein Protokoll und zeigt kein Passwort erneut an.

Abnahme: eine einzelne hochgeladene PHP-Datei genügt, um von einem leeren Webspace zu einer betriebsbereiten Installation zu kommen; eine leere Installation ist im Browser in sechs Schritten betriebsbereit, ohne Konsole. Ein manipuliertes Archiv (falsche Prüfsumme) wird abgelehnt, und ein Archiveintrag mit .. im Pfad landet nirgends. Ein zweiter Aufruf von /install liefert 404. Die Systemprüfung schlägt bei fehlenden Schreibrechten fehl und sagt, welcher Pfad fehlt. Getestet gegen ein leeres Verzeichnis und eine leere Datenbank, nicht gegen die Entwicklungsinstallation.

O – Sicherung und Wiederherstellung als Funktion (Nutzerauftrag vom 12.09.2026)

Braucht: nichts. Für MySQL zusätzlich die Hostingentscheidung aus Arbeitspaket J.

Umut: „Todo aufnehmen. Standard backup funktion.“ Heute gibt es Sicherungen nur als Nebenprodukt: der Updater kopiert die Datenbank vor einem Update, und ich habe vor jeder Migration von Hand eine Kopie in var/backups/ gelegt. Das ist keine Sicherungsfunktion, das ist Disziplin – und Disziplin fällt aus, sobald jemand anderes arbeitet.

Was in eine Sicherung gehört, und was nicht:

  • Die Datenbank. Bei SQLite die Datei, und zwar konsistent: eine Kopie während eines Schreibvorgangs kann unbrauchbar sein, deshalb VACUUM INTO statt copy(). Bei MySQL mysqldump.
  • Der Medienordner. Ohne ihn ist die Sicherung wertlos: die Dokumente verweisen auf Bild-IDs, die Dateien liegen im Medienordner. Das ist auch der Teil, der groß wird.
  • Ein Manifest: Zeitpunkt, Fassung aus VERSION, höchste angewandte Migration, Anzahl Seiten, Fassungen und Medien, und eine Prüfsumme je Teil. Ohne das weiß man beim Zurückspielen nicht, ob die Sicherung zum Code passt.
  • Nicht .env. Darin steht APP_SECRET; eine Sicherung, die man herunterlädt und weitergibt, darf das nicht enthalten. Die Datei wird beim serverseitigen Aufbewahren mitkopiert (der Updater tut das schon), aber nicht in ein Archiv gelegt, das den Server verlässt. Beim Zurückspielen wird sie deshalb auch nicht überschrieben.

Ablauf einer Sicherung: Wartungszustand ist nicht nötig – VACUUM INTO liefert einen konsistenten Stand, und Medien werden nur hinzugefügt, nie verändert. Geschrieben wird nach var/backups/sicherung-<zeitstempel>/ mit Manifest; auf Wunsch als ein Archiv. var/ liegt außerhalb des Dokumentenstamms (public/), also ist keine Sicherung über das Netz erreichbar – das muss die Systemprüfung nachweisen, nicht annehmen.

Aufbewahrung mit Rotation: die letzten sieben täglichen, vier wöchentlichen, drei monatlichen. Ohne Rotation läuft die Platte voll und dann scheitert die nächste Sicherung genau dann, wenn man sie braucht.

Herunterladen: in der Verwaltung, als Archiv, gestreamt. Mit zwei ehrlichen Hinweisen: ohne .env, und bei einem großen Medienordner bricht ein Browserdownload womöglich ab – dann ist die Konsole oder ein Dateizugriff der Weg. Kein Archiv im Speicher aufbauen.

Wiederherstellung ist der Teil, der zählt, und der Teil, den niemand übt:

  1. cms:restore <verzeichnis-oder-archiv>, und in der Verwaltung nur die Anzeige, welche Sicherungen es gibt. Ein Knopf, der die Datenbank überschreibt, gehört nicht neben einen Knopf, der eine Seite speichert.
  2. Manifest gegen die laufende Installation prüfen: eine Sicherung aus einer neueren Fassung wird abgewiesen – deren Migrationen kennt der alte Code nicht. Eine ältere ist erlaubt, dann läuft danach die Migration.
  3. Wartungszustand an, Sicherung des aktuellen Stands anlegen (auch eine Wiederherstellung kann die falsche sein), Datenbank ersetzen, Medien ergänzen statt löschen, Migration, Cache, Rauchtest, Wartungszustand aus.
  4. Protokoll nach var/backups.log.

Was gebaut wird: src/Ops/Backup.php, cms:backup (mit --rotate, --archive), cms:restore (mit --force), eine Übersicht im Bildschirm „System“ mit Größe, Zeitpunkt, Fassung und Prüfsummenstatus je Sicherung, und ein Eintrag im Cronjob-Abschnitt des README.

Abnahme – und hier liegt der offene Punkt aus Arbeitspaket I: eine Wiederherstellung ist nachgewiesen. Nicht „die Funktion existiert“, sondern: eine Sicherung wird angelegt, die Datenbank wird nachweislich verändert (eine Seite gelöscht, ein Bild entfernt), die Sicherung wird zurückgespielt, und danach ist beides wieder da – geprüft im Test gegen eine wegwerfbare Installation und einmal von Hand auf der Entwicklungsinstallation. Eine Sicherungsfunktion, deren Wiederherstellung nie gelaufen ist, ist eine Vermutung.

K – Migration von kant-it.de

Braucht: B, C, D, E, F, H, J und Backendzugang.

Export und Import für Seiten, Beiträge, Medien, Adressen und SEO-Daten. Flatsome-Shortcodes inventarisieren und die tatsächlich vorkommenden Typen auf eigene Blöcke abbilden; unbekannte Inhalte im Importbericht ausweisen und erhalten, nicht verwerfen. Adressen, Medienverweise und interne Links vor und nach dem Umzug vergleichen. Weiterleitungen für alle bestehenden Adressen. Backup und Wiederherstellung erproben, Rückweg zur bisherigen Website vorbereiten.

Abnahme: kant-it.de ist vollständig geprüft migriert, der Rückweg ist erprobt. Der Livegang erfolgt erst, wenn Umut ihn ausdrücklich beauftragt.

Bewusst nicht in Version 1

Shop und Buchungen, Mehrmandantenbetrieb, zentrale Verwaltung mehrerer Installationen, öffentlicher Pluginmarktplatz, Kommentare, Mehrsprachigkeit über das Datenmodell hinaus und redaktionelle KI. Diese Punkte stehen im Entwicklungsplan als spätere Phasen und werden erst nach dem Pilotbetrieb anhand des tatsächlichen Bedarfs geplant.

Zum Aufwand

Der Entwicklungsplan sieht eine Aufwandsschätzung nach dem ersten durchgängigen Prototyp vor. Belastbar schätzen lässt sich erst, wenn der Backendzugang zum Piloten vorliegt: der tatsächliche Beitragsbestand, die Anzahl der Flatsome-Shortcode-Typen und der Umfang von kant-nova-chat bestimmen den Aufwand der Pakete B, H und K erheblich. Ohne diese Angaben wäre jede Zahl hier eine Erfindung.