Schriften ins Repository, Anleitung fuer neue Websites, Schutzkopfzeilen
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

Die Lato-Dateien liegen jetzt unter public/assets/fonts/ samt OFL-Text
und einer Lizenznotiz: auf einem frischen Server fehlt nichts, und es
wird nie versehentlich von Google geladen.

docs/NEUE-WEBSITE.md beschreibt, wie eine neue Kundenwebsite entsteht -
Reihenfolge, Dokumentformat, Bilder, SEO, Pruefungen - und nennt am
Ende die fuenf Fehler, die hier immer wieder passiert sind.

Permissions-Policy ergaenzt (Kamera, Mikrofon, Standort, Sensoren aus),
X-Powered-By entfernt: der Server nannte in jeder Antwort seine genaue
PHP-Fassung. Die Browserpruefung misst die Kopfzeilen jetzt.

Behoben: der Editor kannte Schema v17, der Validator v18 - die
Beitragsnavigation liess sich in keinem aktuellen Dokument einfuegen.
Ein Test bindet die Zahl des Editors an die des Validators.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-13 15:28:31 +02:00
1 parent 7c6b2a9ec8
commit a2dfd0f796
20 files changed
+401 -3

No files matched your search

+201
View File
@@ -0,0 +1,201 @@
# Eine neue Website mit KantSites bauen
Diese Anleitung richtet sich an den, der die Arbeit macht – Mensch oder Assistent. Sie sagt, in
welcher Reihenfolge etwas entsteht, welche Entscheidung wohin gehört und welche Fehler dieses
Projekt schon einmal gemacht hat. Wer sie befolgt, kommt ohne Umwege zu einer Website, die die
Prüfungen besteht.
**Lies zuerst:** `AGENTS.md` (Projektregeln), `docs/STATUS.md` (was es gibt und was fehlt),
`docs/ARCHITECTURE.md` (warum es so ist). Diese drei sind der Stand der Dinge; diese Datei hier ist
das Kochbuch.
---
## 0. Die Grundregel
**Alles, was eine Website ausmacht, steht in der Datenbank – nicht im Code.** Seiten, Kopf- und
Fußbereich, Menüs, Formulare, Farben, Schriften, Einstellungen. Wer für eine neue Website eine
Twig-Datei anfasst oder CSS in `public/assets/site.css` schreibt, hat sich verlaufen: dann ändert
sich das Aussehen *aller* Installationen, und beim nächsten Update ist es weg.
Ausnahme mit Ansage: eigenes CSS und eigenes Skript je Seite gibt es im Editor (Bereich „Code"),
und es landet ebenfalls in der Datenbank.
---
## 1. Installation aufsetzen
```bash
# Eigene Datenbank und eigener Medienordner - nie gegen die Entwicklungsinstallation arbeiten.
DATABASE_URL="sqlite:///…/var/kunde.db" php vendor/bin/doctrine-migrations migrate --no-interaction
DATABASE_URL="sqlite:///…/var/kunde.db" MEDIA_DIRECTORY="…/var/kunde-media" php bin/console cms:user:create
```
Oder im Browser über `/install` – sechs Schritte, legt Startseite, Kopf, Fuß, Menü, Impressum und
Datenschutzgerüst gleich mit an.
Zum Ansehen ein eigener Server auf einem eigenen Port:
```bash
DATABASE_URL="sqlite:///…/var/kunde.db" MEDIA_DIRECTORY="…/var/kunde-media" \
SITE_ORIGIN=http://127.0.0.1:8090 php -S 127.0.0.1:8090 -t public bin/dev-router.php
```
`bin/dev-router.php` ist Pflicht: ohne ihn beantwortet der eingebaute Server `/sitemap.xml` und
`/robots.txt` selbst, und die Dateien kommen ohne die Kopfzeilen, die später `public/.htaccess`
setzt – jede Messung wäre dann falsch.
---
## 2. Inhalte anlegen: als Skript, nicht von Hand
Eine vollständige Website besteht aus Dutzenden Blöcken mit Hunderten Gestaltungswerten. Das klickt
niemand zweimal. **Schreibe ein Aufbauskript**, das Medien hochlädt, Dokumente zusammensetzt und
veröffentlicht – Vorlage ist das Skript, mit dem kant-it.de nachgebaut wurde.
Drei Eigenschaften, die ein solches Skript haben muss (alle drei sind aus Schaden gelernt):
1. **Wiederholbar.** Ein zweiter Lauf aktualisiert vorhandene Seiten, statt sie zu überspringen –
sonst ändert eine Korrektur im Skript nichts an der Website. Bilder werden am Titel
wiedererkannt, sonst wächst die Mediathek mit jedem Lauf.
2. **Eine Quelle für Adressen.** Verweise nie von Hand schreiben. Die Adresse eines Beitrags
entsteht aus derselben Funktion, die den Beitrag anlegt. Im Nachbau standen vier Links im Hero
von Hand darin – alle vier tot, weil die Beiträge längere Adressen bekamen.
3. **Über die Repository-Klassen, nicht per SQL.** `PageRepository::saveDraft()` und `publish()`
prüfen das Dokument. Ein per `INSERT` eingeschmuggeltes Dokument kann Felder enthalten, die der
Editor nie wieder öffnen kann.
Reihenfolge im Skript: Theme → Medien → Kopf- und Fußbereich → Seiten → Beiträge → Menüs →
Formulare → Einstellungen.
---
## 3. Das Dokumentformat
Ein Dokument ist `{schemaVersion, title, description, blocks: [...]}`. Ein Block ist
`{id, type, props, style, layout, children}`.
- **`id`** – höchstens 64 Zeichen, eindeutig im Dokument, erscheint als HTML-`id` und als
CSS-Selektor. Sprechend wählen (`hero-headline`, nicht `block-7`).
- **`type`** – siehe Liste im Editor; `section`, `row`, `column`, `heading`, `text`, `image`,
`button`, `postlist`, `slider`, `tabs`, `form`, `contact`, `stat`, `quote`, `iconcard`, …
- **`props`** – der Inhalt. Was hier steht, ist Text und Daten, nie Auszeichnung mit Wirkung.
- **`style`** – `{desktop: {...}, tablet: {...}, mobile: {...}}`, jeder Wert aus `StyleSchema`.
Seiten, Abstände und Ecken sind **Objekte** (`['top' => '10px', …]`), keine Kurzschreibweise.
- **`layout.effects`** – `{entrance: 'rise', delay: 'short'}` für das Einblenden beim Scrollen.
**Setze `schemaVersion` auf die aktuelle Fassung** (siehe `DocumentValidator::SCHEMA_VERSION`),
sonst sind neuere Bausteine und Eigenschaften nicht erlaubt – und der Editor kann in einem zu alten
Dokument nichts einfügen.
---
## 4. Gestaltung
**Erst das Theme, dann die Blöcke.** Farben und Schriften gehören in die Palette
(`/admin/theme`): ein Block, der `@accent` sagt, ändert sich mit dem Theme; einer, der `#c1121f`
sagt, nicht. Die Palette färbt auch Knöpfe, Symbole, Fokusringe und den Einwilligungsdialog.
Feste Größen nur, wo sie gemeint sind. `maxWidth` ohne `margin: auto` zentriert nichts – das war
hier schon zweimal die Ursache eines „geht nicht".
**Die Vorschau im Editor zeigt dieselbe Ausgabe wie die Website.** Was dort falsch aussieht, ist
falsch – nicht „nur die Vorschau".
---
## 5. Bilder
- Hochladen über `MediaLibrary::upload()`; daraus entstehen WebP-Varianten in 480/960/1600/2400 px
(nie größer als das Original). Das Original wird nicht behalten.
- **Vor dem Hochladen prüfen:** über 12 Megapixel oder 8000 px Kantenlänge werden abgelehnt, und
ein Hostingpaket mit 128 MB Speicher schafft große Bilder nicht. Vorher auf 2400 px verkleinern.
- Im Dokument immer alle `sources` mitgeben, damit `srcset` entsteht.
- Ein Bild mit fester Anzeigebreite bekommt `style.desktop.maxWidth` – daraus entsteht das
`sizes`-Attribut, und der Browser lädt die kleine Variante. Ohne das lud ein 190-px-Logo die
960er Datei.
- Hintergrundbilder werden je Gerät verkleinert, auch wenn sie nur einmal gesetzt wurden.
---
## 6. Beiträge
Optional. Gibt es keine, verschwinden Archiv, Feed und Feedverweis von selbst; ein
Beitragslisten-Baustein zeigt öffentlich nichts.
Beim Anlegen wählt man einen von drei Aufbauten (`PostLayouts`: Bildkopf, Klassisch, Magazin), für
die Liste eine von vier Darstellungen (Karten, Liste, Kompakt, Wechselseitig). Beides sind
Startpunkte, keine Schablonen.
---
## 7. SEO
Meistens fertig, wenn der Inhalt stimmt: Titel, Beschreibung, Canonical, Open Graph, strukturierte
Daten, Sitemap, `robots.txt` entstehen aus dem, was dasteht. Zu setzen bleibt:
- **Titel für Suchergebnisse** je Seite, wenn er sich vom Titel auf der Seite unterscheiden soll.
- **Vorschaubild beim Teilen** – sonst nimmt ein Beitrag sein Beitragsbild und eine Seite gar nichts.
- **Unternehmensdaten** unter Einstellungen; daraus entsteht der strukturierte Datensatz.
- **Weiterleitungen**, wenn die Website eine alte ablöst. **Vor dem Umzug** die Adressliste der
alten Website besorgen (deren `sitemap.xml`) und für jede Adresse, die sich ändert, eine
Weiterleitung anlegen. Ohne das ist der Rang von Jahren weg.
---
## 8. Schriften
Nach dem Aufsetzen **einmal** `php bin/console cms:fonts:fetch` (oder der Knopf im Designbereich).
Ohne das lädt die Website die Schriften von Google – zwei fremde Verbindungen nacheinander und die
IP-Adresse jedes Besuchers an einen Dritten. Die Dateien der Pilotwebsite liegen im Repository;
wählt eine neue Website andere Schriften, holt dieser Befehl sie.
---
## 9. Prüfen
In dieser Reihenfolge, gegen die neue Website:
```bash
CMS_URL=http://127.0.0.1:8090 npm run test:links # kein toter Verweis
CMS_URL=http://127.0.0.1:8090 npm run test:browser # Barrierefreiheit, Tastatur, ohne JavaScript, Ladebudget
MSYS_NO_PATHCONV=1 CMS_URL=http://127.0.0.1:8090 CMS_PATHS="/,/leistungen,/kontakt" npm run audit
```
Für `npm run audit` muss die Installation im **Produktionsmodus** laufen (`APP_ENV=prod`,
`APP_DEBUG=0`), sonst misst man den Debugmodus.
Am Code geändert? Dann zusätzlich `php vendor/bin/phpunit` (beide PHP-Fassungen),
`npm run test:editor` und `composer format`.
**Zielwerte:** Barrierefreiheit, Best Practices und SEO je 100. Performance mindestens 95, 100
anstreben – eine bilderreiche Startseite mit Slider landet realistisch bei 96–98.
Findet die Prüfung einen Kontrastmangel aus einer im Editor gesetzten Farbe, wird er **gemeldet und
nicht behoben**: das ist eine Entscheidung des Redakteurs, keine des Codes.
---
## 10. Livegang
`docs/LIVEGANG.md` ist die Liste. Die drei Dinge, die dort am häufigsten vergessen werden:
E-Mail-Versand einmal wirklich ausprobieren, Schriften holen, und die Weiterleitungen der alten
Adressen prüfen.
---
## Was in diesem Projekt immer wieder schiefging
Wer diese fünf kennt, spart sich die Suche:
1. **Eine Einstellung, die speichert und nichts tut.** Das Feld war da, die Ausgabe nicht – beim
Vorschaubild fürs Teilen zwei Monate lang. **Nach jedem neuen Feld nachsehen, ob es vorne
ankommt**, und einen Test schreiben, der genau das prüft.
2. **`|default(true)` in Twig macht aus einem gespeicherten `false` ein `true`.** Für Schalter immer
`??` verwenden.
3. **Die Prüfung lief gegen die Demo, nicht gegen die Website.** Feste Pfade und Linktexte in einem
Prüfskript prüfen nichts, sobald der Inhalt anders ist.
4. **Messen statt vermuten.** Das Vorabladen des Hero-Bildes klang richtig und kostete fünf
Punkte. Jede Annahme über Geschwindigkeit wird gemessen, vorher und nachher.
5. **Der Entwicklungsserver ist nicht das Hosting.** Ohne Kompression und Cache-Kopfzeilen misst
man 90 statt 96 – und optimiert an der falschen Stelle.
+4
View File
@@ -34,6 +34,10 @@ Erster Commit angelegt (`82b0782`); das Projekt hat damit einen Rückfallpunkt.
- **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.
- **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.
- **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.