Built while setting up the redesign of ebsolar.de as the sixth local
installation (scripts/seed-ebsolar.php, reset-ebsolar.sh, ports 8096/8097).
The rebuild surfaced four things in the core:
- The icon set knows three arrows now: arrow-right, arrow-left,
arrow-up-right. The button block and the post navigation render them as
inline SVG inside the same aria-hidden span as before, so a template that
hides the arrow with "& span{display:none}" keeps working. Until now the
arrow was a text glyph that looked different in every font. Umut: "bei
alle Projekte bzw bei allen möchte ich dieses emoji icon weg haben".
- Own CSS with the same nested selector twice kept only the last rule.
"& .post-card{border:0}" followed by "& .post-card{transform:none}" lost
the border - silently, which brought the core's card frame back on the
project grid. The parser now joins the declarations in source order, as
CSS does; the same for "&{…}" next to the element's own properties.
- The eager image over the fold was not preloaded. The background image of
the first section has had a preload link for a while; a plain <img> was
found only after the stylesheet was parsed, and that round trip went
straight into LCP. PageController::heroImage() now writes the same src,
srcset and sizes the block template writes, and page.html.twig emits the
link - unless a background image is already preloaded.
- Measured on the way: an entrance effect on the LCP element costs about
900 ms of render delay. The EB Solar hero image therefore has none.
Tests: 581 green, the icon test accepts quoted keys, two cases for the CSS
cascade, the preload asserted in the big-picture test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Umut: "Kannst du bitte fuer die Erstseite bzw. nach der Installation eine
schoene Erstseite erstellen. Under Construction oder so ohne Details mit melde
dich an usw. Am besten auch noch Logo von KantSites."
starterPage() shipped two sections of instructions for the owner: "Melde dich
an, oeffne sie im Editor ..." and "Die naechsten Schritte" with design, menus
and settings. That stood on the only page a fresh installation serves
publicly, so the visitor read it, not the owner - who gets the same steps on
the last installer screen and in the admin area anyway.
It is now the page such a site actually needs: the KantSites mark, the label
"In Arbeit" in the accent colour, the site name as the single H1, "Hier
entsteht eine neue Webpraesenz.", a short rule and the line "Erstellt mit
KantSites". The section is a flex container with min-height 70vh and a
gradient from @paper to @mint, so the little that is there sits in the middle
of the screen; the colours are palette references and follow a design change.
Everything is an ordinary block with ordinary style values and can be changed
or removed in the editor. The page description is a real sentence now instead
of "NOCH AUSZUFUELLEN", so a freshly installed site does not carry a
placeholder into search results.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut on the DEEG hero image: "It should be rounded, but it was done from
somewhere, and the corners look odd. And the editor has no image corner
rounding at all, does it?"
It did have one - per block, under Design > Bild > Bildecken. What it did not
have was the 12px that enhancements.css set on .block-image for every
installation: rounded on screen, present in no field, and only removable by
overriding it on each block. On a template that is square throughout it shows,
because the elements above the image - the gradient, the sign, the blue call
to action - are square and stick out of the rounded corner.
The radius is now theme.base.imageRadius, validated like the container width,
emitted as --k-image-radius and used by the stylesheet. The default stays 12px
so existing sites look the same; 0px makes every image square. The per-block
setting still wins.
DEEG is set to 0px, in the seed script and in the installation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
scripts/ is on the exclusion list of build-release.php, so an installation
that runs from the release archive - the usual case on shared hosting -
would never have seen scripts/repair-doubled-entities.php. The 1.20.1 notes
told everyone to run it, which was only true for a Git working copy.
cms:repair:entities does the same work and ships with the application: a dry
run by default, --schreiben to apply, each change validated the way saving
validates it (pages with the heading rule, areas without, as in
PartialRepository). The script is gone; there is one way to do this now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut: "Beim Startseitenbild wird der Text & umgewandelt, aber in der Ansicht
nach dem Speichern wird & daraus."
RichText::sanitise() treated text without a single tag as plain text and only
escaped it. What the editor sends is HTML, tag or no tag - so "Freudenstadt
& Umgebung" gained one level of encoding on every save (&amp;,
&amp;amp; ...) until the entity stood on the page for everyone to read.
The draft of the kant-it.de home page had already reached three levels.
The same branch carried a second fault of the same kind: nl2br() left the
newline behind the <br>, and the next pass turned it into a second <br> via
breakLines() - one paragraph became two. The branch now decodes first and
replaces the line break instead of adding to it, so it ends up with the same
representation as the DOM branch and both are stable under repeated saves.
scripts/repair-doubled-entities.php cleans up what is already stored: the
published and draft revision of every page and the areas, with a dry run,
each change checked against the validator. Older revisions stay as history.
DEEG: favicon is now its own square mark - the roof gable from the logo,
white on the logo blue, rendered from logo-deeg.png - instead of the centre
crop of the wide logo, which showed the "EE" of the wordmark.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut asked for a block that takes custom HTML, and for an iframe on a new
DEEG offers page. Only one of those was missing.
New core block "Eigenes HTML" (document schema v24): one property, the
markup, up to 20.000 characters, written to the page unchanged. Changing it
is reserved for administrators and compared against what is stored - on
saving, on saving an area, and on restoring an older revision - exactly like
an own script. <script> and <iframe> are refused with the way to the right
place instead of being swallowed by the content security policy: scripts
belong in the script field, frames in the "Externer Inhalt" block, because
only that one asks for consent and puts the host into frame-src (ADR 106).
DEEG: /angebote existed as a draft with placeholder text. It is now built
like the legal pages - small caps line, large light heading, lead on the
tinted band - with the IS24 result list below it and a line pointing to the
portal and to the contact form. Navigation and the offers band on the home
page point there. The consent question is switched on, so the frame loads
only after the visitor agrees, and both the banner and the placeholder carry
the site's own shapes and colours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forms gained steps, conditions, image choices, number fields with units and a
summary step; the DEEG price finder uses all of it. Plugins are manifest plus
Twig: blocks, settings, containers, routes, presets and theme packs, switched
per installation. The review backlog is done: archive pages render through the
normal page pipeline, forms and post lists work inside areas and sections,
repeated sections get their own ids, breakpoints and colours come from the
palette, backup and update work on MySQL and lock each other out.
The task worker no longer shortens a process that has no time limit:
set_time_limit() sets the limit instead of extending it, which turned an
unlimited CLI run into eighty seconds and killed the full test suite far away
from the scheduler.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SITE_ORIGIN alone decides whether the site is www or not: CanonicalHostSubscriber
sends the other spelling there with 301, cms:site:origin writes the setting, the
system screen shows it. The roles now match the role help (editor: drafts,
publisher: live content, admin: design, settings, users, system). The installer no
longer reopens on an unreachable database, the session cookie follows ADMIN_PATH,
percent-encoded credentials no longer break the container, consent only accepts
same-site decisions, JSON-LD is HTML-safe, own CSS stays in its block, plus the
editor, updater, download and public-output findings from the full review
(ADR 099, ADR 100).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Umut, with a card of an icon next to a text as the example: "bei Rows die
Breite ändern, am besten per Drag and Drop im Editor". Rows only ever had
equal columns - both the classic `columns` token and the free `gridColumns`
produce `repeat(n, 1fr)`.
A row now carries the free style `columnWidths`, a list of shares such as
`2 1` or `66.7 33.3`, per device and without a new document schema.
`StyleCompiler::columnTracks()` turns it into `grid-template-columns`,
pads or cuts the shares to the number of columns, and gives a device whose
layout stacks the columns a single track, because the desktop rule carries
an id and would otherwise outrank the class that stacks a phone.
In the editor the border between two side-by-side columns of the selected
row is a handle: dragging restyles the row inline while the pointer moves
and writes one undo step on release; the arrow keys nudge by five points.
The design panel shows the same value as the text field "Spaltenbreiten".
The Playwright editor check drags the handle with the real mouse.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Umut asked where a contact form's mail is sent from: it was the recipient
address, `From` equal to `To`. That breaks as soon as the recipient is a
mailbox at another provider, because most hosts only deliver mail that
claims an address of their own domain, and the provider's SPF/DMARC then
treats it as forged.
Each form now has an optional `sender` setting, validated like the
recipient; empty keeps the old behaviour, so stored forms need no
migration. Delivery uses `sender ?: recipient`; the reply-to field is
untouched. The administration shows the field under the recipient with a
note on why it matters. The pilot launch config moves to port 8091 because
8087 was held by another session.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Forms are an editor now: one card per field with add and remove, the
technical name derived from the label, options only for a choice, and a
new "Aussehen" section with a button label and four colours (field border
and background, button and button text) that travel to the page as one
rule per form inside the hashed style block. A live preview draws the
form with the site's default button and names the text contrast.
Statistics open with six figures (views against the same span before,
average per day, busiest day, pages, referred views, missing addresses),
a chart with grid lines and a title per bar, a weekday chart and ranked
lists with share bars. The design screen shows the palette as colour
cards with seven ready-made palettes, picks the base colours from the
palette, previews every font in itself and the typography as a sample.
The sidebar ends in an account card with role and a logout icon.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A section in the header set to #ffffff00 for the scrolled page still looked
white: the sticky header element paints its own white surface and a shadow
line underneath, so a transparent section showed that surface instead of the
page, and the line could be neither recoloured nor removed.
When a top-level header section defines a background of its own - normal,
hover or scrolled - the header element now gets `has-own-surface` and paints
nothing. A header without its own design keeps the white surface, so it does
not turn see-through over the content. A text colour alone is not a surface,
and a boxed column is not the whole header.
The check is a Twig function rather than a controller variable because four
paths render the header: page, archive, editor preview and area preview.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The back-to-top button was hidden as long as the consent question was open,
on every device - and on a phone the question tends to stay open. Instead
of `display:none` it now anchors itself 12px above the bar wherever the bar
covers its corner: below 800px for the centred card, at every width for the
full-width bar and the corner card. Browsers without anchor positioning keep
hiding it there. The bar renders before the button so it can be an anchor,
and the reserved bottom padding grows to 320px below 620px, where the bar
with its default text is up to 299px tall.
The notice itself gets a face. Settings → Einwilligung now carries layout
(card, bar, corner), shape (soft, round, square - one setting for the box and
its buttons), a heading and four colours, each as #hex or @palette key.
`Theme::cssColor()` resolves both, replacing the copy inside the back-to-top
code; `Consent::styles()` writes the values as `--kant-consent-*` into the
hashed style block. The base layout changes with it: an accent stripe and
shield icon, equal-width buttons, a disclosure with a chevron, underlined
legal links, a two-column bar on wide screens. Declining stays an outlined
button of the same size next to accepting. The archive and the consent page
carry the same variables - the archive had no palette at all until now.
In the editor, rows with an own value are labelled in orange rather than
bold with a dot; the device chips sit 6px apart with the focus ring inside
the gap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fonts were still reachable from Google in two places. The editor and area
previews were handed `googleFonts` and no `localFonts`, so the screen used
most of the day fetched Lato from fonts.googleapis.com on every open; the
public page fell back to the same link whenever the files were missing.
Neither path remains: the template has no Google branch, `googleFontsUrl()`
is gone, and the policies name no foreign font host - `font-src 'self'`
publicly, `style-src 'self'; font-src 'self'` in administration. Without
local files a site now shows the theme's fallback and says so.
Preloading took the first two weights per family. The Kant-IT theme names
Lato 400, 700 and 900 and sets its headings on 800/900, so the one weight
that carried them loaded after the stylesheet and `font-display:swap`
swapped the heading in plain sight. Every weight of the theme is preloaded
now, latin subset, and the links sit before the stylesheet: measured cold,
all three files finish at 168 ms against a first paint at 292 ms.
Open tasks no longer hide on the screen that owns them. A strip above every
management screen names what needs a button and carries it: a new version
leads to System, missing fonts to Design. Neither asks the network - the
version comes from the daily update-check task, the fonts from a look in
the folder.
System and update was unusable on a phone: `.stack` is a grid whose
automatic column grows to its widest content, and one long database path
pushed the page sideways. It gets `minmax(0, 1fr)`, the backup table stacks
below 620px, and the prose is 45% shorter with the fine print folded away.
The device chips drew the current device with an outline, which lies outside
the box, ran under its neighbour and was clipped by the sidebar - it read as
a cut-off red frame. Used devices are filled in `#9d4e0d` now (5.6:1), the
current one carries a border inside the chip.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Settings gain opening hours per weekday and a list of places served;
LocalBusiness emits openingHoursSpecification (equal times grouped) and
areaServed. An accordion can be marked as FAQ (schema v23 property
`faq`), which turns its entries into a FAQPage built from the visible
title and text blocks. Elektro Bernhard gets the real office hours
(Mo–Fr 07:30–17:00), the service area from Karlsruhe to Offenburg and
two more questions. ADR 094.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The icon set had no drawing for people, music or a house - the three pictures
the Tagespflege Moldovan template asks for. They join as `people`, `music` and
`home`; a test keeps the list and the drawings together, because a missing path
renders an empty `<path d="">`: invisible, without an error.
An icon block's own colour never reached its drawing: the embedded SVG carries
the same `block-icon` class as its wrapper and set the accent colour again.
`.block-icon .block-icon{color:inherit}` fixes it at the root.
The menu button becomes a thing you can shape. `burgerLabel` chooses symbol and
text, text only or symbol only (which keeps the name for screen readers), and
the new style group `menu` targets the button itself, the way `img` targets an
image and `controls` the slider. Inside the header the opened menu now lies
under the header across the full width instead of pushing the header apart -
on a phone that looked like a missing navigation.
The back-to-top button no longer depends on `animation-timeline: scroll()`. A
browser that parses the property but does not run the timeline left it in the
first frame of the animation: permanently invisible. Two classes from
scrolled.js decide now, and without a script the button simply stays visible.
Also: the Tagespflege Moldovan rebuild as its own installation, built only by
`scripts/seed-tagespflege-moldovan.php`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Social profiles are maintained once under settings (one HTTPS address per
network, SocialProfiles::NETWORKS) and shown by the new `social` block
(document schema v23) wherever it stands: size, shape, layout, labels,
new tab and an optional subset of networks on the block; colour, hover,
spacing and alignment through the free block styles. The addresses also
feed sameAs in the structured data. Icons are own outline drawings in
the style of the existing icon set. ADR 093.
Elektro Bernhard is rebuilt with a dark hero over the team photo, a
transparent header that turns white on scroll, Lato, cards with shadows
and hover lift, a review slider with six real Google reviews, a dark
solar band, page headers on every subpage and staggered entrance
effects. Social profiles sit in the top bar and the footer. The site is
a second local reference installation next to the Kant-IT pilot
(var/elektro-bernhard.db, launch config on port 8088); the seed script
rebuilds it from scratch.
Checked: PHP suite 492 tests (only the documented old failure remains),
33 JS tests, Twig lint, PHP style, full editor browser check with axe.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Round white arrows left and right, dots bottom right with the active one as
an accent bar, a small pause button and no counter - the design of the hero
slider on kant-it.de. Slider and controls share a positioned shell; arrows
hide on phones. New style property for the active dot colour.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dots, arrows, counter and button background are style properties of the
slider ("Slider-Steuerung"), written as custom properties onto the slider and
onto the control bar the script places after it, so the script-free markers
and the scripted controls share the same colours.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cards, quotes and call-to-action bands side by side in a row are equal in
height again: the stacking margin meant for a column no longer applies inside
a row. The header can carry a second set of colours for the scrolled page,
the Flatsome transparent header: a "scrolled" state next to "hover", limited
to colours, shadow and opacity, marked by a small script only where it is
used, plus a setting that lets the header float over the first section.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The editor follows the Flatsome UX Builder: dark top bar with the device
switch, one sidebar with tabs for elements, structure and page, an element
view with breadcrumbs, property tabs and actions, element cards, a context
menu in the preview and keyboard shortcuts that also work inside the preview.
Entrance and hover effects play for every visitor unless the website chooses
to respect the browser's reduced-motion preference (setting "motion", class
"motion-respect" on <html>). Waiting blocks stay hidden until they scroll into
view and are shown at once when the reader jumps past them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Wiederkehrende Aufgaben ohne Cronjob (ADR 073): Sicherung, Statistik kuerzen,
geplante Veroeffentlichung, Updatepruefung und Aufraeumen laufen als Plan in
der Tabelle scheduled_task - eine Zeile je Aufgabe mit Faelligkeit, Sperre und
letztem Ergebnis. Auf geteiltem Hosting gibt es oft keinen Cronjob; der Plan
haengt stattdessen an Anfragen und an einem Takt aus dem geoeffneten Backend.
Diese Arbeit entstand in einer parallel laufenden Sitzung und lag bis hierher
unveroeffentlicht im Arbeitsverzeichnis: die Releases 1.5.0 bis 1.5.5 wurden
bewusst aus dem jeweiligen Tag gebaut und enthielten sie deshalb nicht.
Vor der Veroeffentlichung geprueft, nicht bloss eingepackt:
- phpunit 452/452, npm test 28/28, npm run test:editor vollstaendig.
- Die Migration 20260914000100 legt nur die Tabelle scheduled_task an und nimmt
sie beim Zurueckrollen wieder weg. Sie fasst keine bestehenden Daten an.
- Seit einer Stunde keine Schreibzugriffe der anderen Sitzung mehr.
Dabei zwei Fehler behoben:
- config/services.yaml verwendet !tagged_iterator. Das ist gueltiges Symfony,
aber "lint:yaml config" kennt eigene Tags nur mit --parse-tags und gab 1
zurueck - der CI-Lauf waere daran gescheitert. Flag ergaenzt.
- AdminHeadersSubscriber.php (aus 1.5.3) entsprach nicht dem Stil von
php-cs-fixer; composer lint gab 8 zurueck. Behoben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 fuer jede .js-Datei Cache-Control: immutable, max-age=31536000 - ein
Jahr. Die Fassungskennung haengt aber nur an den Einstiegsdateien, die die
Vorlage schreibt. Die fuenf Module, die der Editor ueber import './...js'
laedt, 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, und editor.js importiert canvas-overlay.js in Zeile 5;
bricht der Import, laeuft keine Zeile danach und die Elementliste bleibt leer.
Das gemeldete fehlende Menue war der Werkzeugkasten, nicht die
Verwaltungsnavigation - die wurde geprueft und ist in Chrome 152 in Ordnung.
Behebung: eine Importkarte in base.html.twig haengt die Fassung an die
aufgeloeste Adresse jedes Moduls, ohne dass eine Importzeile sie kennen muss.
Das Zwischenspeichern ueber ein Jahr bleibt - 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; csp_nonce() gibt ihn der Karte mit. Der Nonce
erlaubt genau dieses eine Skript, 'self' gilt unveraendert fuer alles andere.
Geprueft: der Browsertest verlangt an jeder angeforderten .js-Adresse eine
Fassungskennung (vorher fehlten fuenf, jetzt tragen alle acht), dazu ein Test,
der jeden Modulimport gegen die Importkarte haelt - Gegenprobe gemacht.
npm test 28/28, phpunit 452/452, npm run test:editor vollstaendig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Nachtraege zu 1.4.5, beide aus der Generalprobe.
- Jeder Schritt des Archiv-Updaters geht sofort nach var/updates.log,
waehrend des Laufs und nicht am Ende. Auf Umuts Hosting war die
Ergebnisseite tot, und damit war auch verloren, was der Updater
berichtet hatte. Genau so wurde der naechste Fehlschlag dann doch
lesbar: "bin liess sich nicht beiseitelegen - der alte Stand wurde
zurueckgeschoben" (ein Windows-Artefakt der Testumgebung, und
zugleich der Beweis, dass der Rueckweg funktioniert).
- Die Ergebnisseite versteht auch die Schreibweise bis 1.4.4. Das ist
kein Nachgeben, sondern noetig: Beim Update schreibt der alte Code
die Schritte in die Sitzung und der neue zeigt sie an. Eine Fassung,
die nur die neue Schreibweise kennt, koennte das Update, das sie
selbst einspielt, nicht berichten.
Nachgestellt mit einer echten Installation aus dem Releasearchiv, die
ueber die Oberflaeche aktualisiert wurde - nicht ueber die Konsole.
Geprueft: 434 Tests auf PHP 8.2 und 8.4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alle drei von Umut auf seinem Hosting gefunden, keiner davon auf
meinem Rechner sichtbar.
- Der Updateknopf endete mit 500. Das Update lief vollstaendig durch -
alle neun Schritte, Dateien getauscht, var/ und .env unberuehrt -,
aber die Ergebnisseite starb daran: der Updater ueber Git nennt
einen Schritt "step", der ueber das Archiv nannte ihn "label", beide
legen ihn unter demselben Sitzungsschluessel ab, und die Vorlage
liest "step". Damit war auch der zweite Knopf unerreichbar. Zehn
Tests prueften das Rueckgabe-Array, keiner die Seite, die es
anzeigt; der neue Test rendert sie (ADR 071).
- Die Verwaltung zeigte Git-Fehler auf einer Installation ohne Git.
"git ist installiert" und "das hier ist eine Arbeitskopie" waren
derselbe Schalter; auf dem Hosting ist Git vorhanden, die
Installation kam aber aus dem Archiv. Dort stand "fatal: Not a git
repository" an der Stelle der Fassungsnummer. Ohne Arbeitskopie
verschwinden die Git-Karten jetzt (ADR 072).
- "Sicherung anlegen" endete mit 500: VACUUM INTO gibt es erst seit
SQLite 3.27, das Hosting hat eine aeltere Fassung - alt genug fuer
die Installation, zu alt fuer die Sicherung. Bei aelteren wird jetzt
unter einer Schreibsperre kopiert, und der Knopf faengt jeden Fehler
ab statt mit einer 500-Seite zu enden (ADR 072).
Nachgestellt wurde das mit einer echten 1.4.3-Installation aus dem
Releasearchiv, eigener Datenbank, eigenem Port. Der Archiv-Updater
laeuft dort vollstaendig durch: 1.4.3 -> 1.4.4, neun Schritte, var/
und .env unberuehrt.
Geprueft: 433 Tests auf PHP 8.2 und 8.4, Editor-Browsertest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Luecken, die erst das Veroeffentlichen von 1.4.3 gezeigt hat.
- Es gab keinen Weg, ausstehende Migrationen im Browser auszufuehren.
Wer die Dateien per FTP hochlaedt - und das muss genau einmal jeder
tun, dessen Installation die Updatekarte noch nicht kennt -, hatte
danach neuen Code und eine alte Datenbank, und dafuer nur die
Konsole. Also genau das, was auf geteiltem Hosting fehlt. Der
Bildschirm "System" zeigt jetzt eine Karte, sobald etwas aussteht,
nennt die Namen und fuehrt sie auf Knopfdruck aus.
- "Die Releaseseite antwortet nicht" waren drei Ursachen in einem
Satz. Beim Gegenpruefen des frischen Releases holte der Updater von
hier gar nichts - nicht weil die Leitung zu waere, sondern weil
diesem PHP der Zertifikatsspeicher fehlt. Auf einem Webhosting ist
das der haeufigste Grund und der einzige, den man in einer Minute
behebt; er steht jetzt in der Meldung. Bei genau diesem Fehler wird
zusaetzlich der Weg ueber den Dateizeiger versucht - damit
funktioniert der Abruf auf diesem Rechner. Abgeschaltet wird die
Pruefung nicht: ein Update ohne Zertifikatspruefung koennte
unterwegs ausgetauscht worden sein.
Dazu der manuelle Weg Schritt fuer Schritt in docs/UPDATE.md und ein
Test, der beides festhaelt: dass die Karte auffaellt und dass nur ein
angemeldeter Administrator mit gueltigem Formular migrieren kann.
Geprueft: 431 Tests auf PHP 8.2 und 8.4, Editor-, Browser- und
Linkpruefung, Editor-Modelltests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vier Wuensche und ein Fund beim Nachmessen.
- Farbwaehler im Designbereich. Das Textfeld bleibt massgeblich, damit
"@akzent" ein Verweis auf die Palette bleibt und nicht beim ersten
Klick zu einem festen Hexwert erstarrt (ADR 066).
- Die Verwaltung auf dem Telefon: das Menue klappt unter 760 px zu und
nennt den Bereich, in dem man steht; Ziele 44 px, kein Skript.
- Eigene SVG-Symbole statt Unicode-Zeichen - ein Telefon ersetzt die
sonst durch Emoji in fremdem Stil. Zwei Eintraege trugen dabei
denselben Pfad; ein Test findet beides (ADR 067).
- Update aus dem Releasearchiv: der Weg fuer Installationen ohne Git.
Zwei Schritte, weil ein Prozess, der sich selbst ausgetauscht hat,
mit dem alten Code weiterarbeitet. var/, .env, hochgeladene
Schriften und das erzeugte Buendel bleiben unberuehrt; public/ wird
dazu Datei fuer Datei zusammengefuehrt. Geprueft ohne Netz gegen
eine Releaseseite im Speicher (ADR 068).
Beim Nachmessen der echten Startseite statt einer Testseite: das
groesste Element ueber der Falz ist dort kein <img>, sondern der
Hintergrund des ersten Abschnitts - und der steht nur im Stylesheet.
Drei Vorabverweise mit denselben Geraetegrenzen wie der Compiler holen
genau die Variante, die die Regel auch nimmt. LCP 1,3 s -> 1,1 s.
Dabei gefunden: dasselbe Bild wurde je Geraeteregel neu abgefragt -
Startseite 19 -> 15 Abfragen (ADR 069).
Geprueft: 428 Tests auf PHP 8.2 und 8.4, Editor-, Browser- und
Linkpruefung, Lighthouse auf drei echten Seiten (alles ausser der
Leistung der Startseite 100).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auf dem Hosting brach die Installation im dritten Schritt ab: near
"(": syntax error. Dort laeuft SQLite in einer Fassung vor 3.16, und
Doctrine liest das Schema mit Tabellenfunktionen wie
pragma_table_info(...), die es erst seit 3.16 gibt.
Die Pruefung nennt die gefundene Fassung jetzt 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 auffaellt, ist keine Pruefung wert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
php bin/console cms:admin:path <neu> legt die Verwaltung auf eine
andere Adresse. Geprueft wird, dass der Name frei ist und keine Seite
verdeckt; der Wert steht in .env.local und fuellt einen Parameter, der
als Praefix in jeder Verwaltungsroute steht, in der Firewall, in den
Schutzkopfzeilen und in der Statistik.
Die robots.txt nennt einen geaenderten Namen nicht - sie ist oeffentlich
und waere sonst ein Wegweiser. Beim Standardnamen bleibt die Zeile,
weil diese Adresse ohnehin jeder kennt.
Die Auffangroute fuer Seiten steht jetzt zuletzt: sie kann einen frei
gewaehlten Namen nicht per Regex ausschliessen, und die Reihenfolge ist
ohnehin die richtige. Der Befehl raeumt den Zwischenspeicher auf, aber
nicht den, in dem er selbst laeuft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Knopf "nach oben" ist ein Anker, kein Skript: er springt an den
Seitenanfang, eingeblendet ueber eine Scroll-Zeitachse. Abschaltbar und
vollstaendig einstellbar - Seite, Form, Groesse, Farben, Schwelle,
Beschriftung.
Bilder lassen sich vergroessern, je Bild waehlbar (nichts / vergroessern
/ Adresse), Galerien mit einem Schalter. Grundlage ist ein gewoehnlicher
Verweis auf die grosse Fassung: ohne JavaScript oeffnet er das Bild, mit
JavaScript einen dialog-Betrachter mit Blaettern, Escape und Pfeiltasten.
Das Skript laedt nur, wo es etwas zu tun hat.
Die Rechtszeile wird ein Baustein und steht damit im Fussbereich statt
als heller Streifen darunter. Die eingebaute Leiste erscheint nur noch,
wenn die Seite den Baustein nicht enthaelt.
Dabei gefunden: zwei Stylesheet-Regeln griffen seit drei
Veroeffentlichungen nicht mehr. Beim Entfernen alter Regeln waren
Selektorreste stehen geblieben, und CSS nimmt dann die naechste Regel
mit - betroffen waren der Einwilligungsdialog und die Rechtszeile. Ein
Test prueft jetzt jede CSS-Datei auf abgebrochene Selektoren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In der wechselseitigen Beitragsliste stand "Weiterlesen" als reine
Beschriftung da: sichtbar wie ein Knopf, aber nicht anklickbar. Jetzt
ein Link, mit dem Beitragstitel im Linktext fuer Vorlesesoftware.
Jedes Menue holte beim Aufloesen die vollstaendige Seitenliste selbst.
Mit zwei Menuen fiel das nicht auf, mit vier - Kopf, zwei Listen im
Fuss, Rechtszeile - waren es vier gleiche Abfragen je Seitenaufruf.
Einmal je Anfrage gemerkt: 21 -> 18 Abfragen auf der Startseite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Turnstile laesst sich in den Einstellungen einschalten: aus, bis beide
Schluessel eingetragen sind; die Sicherheitsrichtlinie oeffnet sich nur
auf Seiten mit Formular, und nur fuer challenges.cloudflare.com.
Antwortet Cloudflare nicht, gilt die Absendung als bestanden - ein
Ausfall dort darf keine Kundenanfrage kosten. Die drei eingebauten
Pruefungen ohne Dritte bleiben davon unberuehrt.
Dabei gefunden: wer ein Pflichtfeld vergass, bekam die Seite ohne
Gestaltung zurueck. Der Formularcode baute sie selbst ein zweites Mal
auf und liess Theme, Blockstile und Schriften weg. Jetzt geht auch
dieser Weg durch den Seitencontroller, die Meldungen reisen als
Anfrageattribut mit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Getrennter Titel fuer Suchergebnisse je Seite und Beitrag (Migration
20260913000100): auf der Seite steht "Leistungen", im Suchergebnis darf
der lange Titel stehen. Leer heisst weiterhin: der Titel der Seite.
Schriften kommen jetzt vom eigenen Server. Ein Knopf im Designbereich
oder cms:fonts:fetch holt die gewaehlten Google-Schnitte als WOFF2 nach
public/assets/fonts. Das spart zwei fremde Verbindungen nacheinander
(gemessen 820 ms) und die IP-Weitergabe an Google. Ohne Dateien bleibt
es beim bisherigen Verweis - eine Seite ohne Schrift waere schlimmer.
Ein Stylesheet statt vier, mit Pruefsumme im Namen und ohne Kommentare,
dazu eine .htaccess mit Kompression, Cache und Schutz interner Dateien.
Der Entwicklungsserver sendet dieselben Kopfzeilen, sonst misst man
lokal etwas, das es im Betrieb nicht gibt.
Bilder in der Groesse, in der sie erscheinen: ein nur fuer Desktop
gesetztes Hintergrundbild gilt auf dem Telefon in Telefongroesse
(189 KB -> 40 KB), img nennt seine feste Breite oder sizes="auto", und
fetchpriority liegt auf dem ersten Bild des Inhalts statt auf dem Logo.
Neu: npm run test:links prueft jeden internen Verweis auf seine
Antwort. Lighthouse gegen den Nachbau: Barrierefreiheit, Best
Practices und SEO 100 auf allen vier geprueften Adressen, Performance
100 ausser auf der bilderreichen Startseite (98 mobil, 96 Desktop).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Feld "Vorschaubild beim Teilen" gab es seit dem SEO-Paket,
ausgegeben wurde es nie: keine mit KantSites gebaute Seite hatte ein
og:image. Es wird jetzt aufgeloest - erst das gesetzte Vorschaubild,
sonst das Beitragsbild - als absolute Adresse bis 1600 px, dazu
og:site_name, og:locale, og:image:alt und twitter:card (klein ohne
Bild, gross mit). Dasselbe Bild steht im strukturierten Datensatz
eines Beitrags; Archive haben jetzt ebenfalls Open-Graph-Angaben.
Eigene robots.txt-Zeilen: 200 statt 30, und Leerzeilen bleiben als
Trenner erhalten - Regeln je Bot (welche KI-Crawler lesen duerfen)
brauchen Bloecke, nicht einen Klumpen.
Behoben: die Symbolerzeugung suchte feste Breiten statt der Varianten,
die das Bild wirklich hat - ein 192 px grosses Zeichen, also der
haeufigste Fall, scheiterte damit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Website ohne Beitraege redet nirgends von Beitraegen: Archiv und
Feed antworten mit 404, der Feedverweis faellt aus dem Kopf, und der
Hinweiskasten der Beitragsliste bleibt im Editor statt oeffentlich zu
erscheinen. Das gilt auch je Kategorie, sobald deren letzter Beitrag
weg ist. Dabei gefunden: die Liste fragte je Beitrag nach Bild und
Begriffen - jetzt je eine Abfrage fuer die ganze Liste (22 -> 16
Abfragen auf der Startseite des Nachbaus).
Unterstrichene Links gibt es nur noch im Fliesstext, dort mit einem
Schalter im Designbereich; Karten, Menue, Kontaktzeilen und der
Ansehen-Streifen haben keinen mehr. Die aktuelle Seite im Menue wird
ueber das Schriftgewicht kenntlich - die Akzentfarbe fiel im dunklen
Fussmenue durch die Kontrastpruefung.
Neu: das Symbol der Website laesst sich festlegen. Aus einem Bild der
Mediathek entstehen beim Speichern quadratische PNG-Dateien in 32 und
180 px; schlaegt das fehl, wird die Einstellung nicht gespeichert.
Die Browserpruefung laeuft jetzt gegen jede Installation statt nur
gegen die Demo: geprueft wird, was in der Sitemap steht, und das
Ladebudget trennt Bilder vom Beiwerk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut: "Effekte gibts keine, sind die aktiv?" Nachgemessen: die
Zeigereffekte liefen alle. Die Einblendungen gab es seit Schema v3 - nur
benutzt hatte sie niemand, weil eine Animation beim Laden unterhalb der
Falz wertlos ist.
Mit animation-timeline: view() laufen dieselben Keyframes, wenn der
Abschnitt in den Blick kommt - hinter der Bewegungsschranke, hinter
@supports (Firefox und Safari behalten das Einblenden beim Laden) und mit
einem Bereich, der endet, bevor der Abschnitt in der Mitte steht.
Dabei hat axe einen Fehler gefunden, den ich selbst gebaut hatte: eine
Scroll-Zeitachse ist umkehrbar, also stand alles unterhalb der Falz
dauerhaft am Anfang der Kurve - bei opacity .35 blass, bei .85 immer noch
3,86:1 statt 7,1:1. Auf dem Scrollweg wird deshalb nur Bewegung animiert,
keine Deckkraft.
Benutzt wird es jetzt auch: alle drei Beitragsvorlagen bringen die
Einblendung je Abschnitt mit.
Geprueft: 400 Tests unter PHP 8.2 und 8.4, Editortest, Browserpruefung,
axe ohne Verstoss auf Startseite, Projekteseite und Beitrag - vor und
nach dem Durchscrollen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Karten (Raster mit Bild oben), Liste (Bild links, Text rechts), Kompakt
(nur Titel und Datum) und Wechselseitig (Bild mal links, mal rechts).
Dieselben Daten, dasselbe Markup - nur eine Klasse wechselt.
Die erste Fassung von "Wechselseitig" war ein umgedrehter Flexkasten und
sah auch so aus. Jetzt sitzt das Bild in einer Karte mit Ecken, Schatten
und festem Verhaeltnis, der Text hat eine Nummer aus einem CSS-Zaehler,
eine begrenzte Zeilenlaenge und einen sichtbaren Verweis, und zwischen den
Reihen liegt eine feine Linie.
Zwei Details: die Karte ist das Listenelement, also zaehlt :nth-child auf
ihr - die erste Fassung wechselte deshalb gar nicht. Und die Umkehrung
laeuft ueber order statt row-reverse, damit auf dem Telefon sauber wieder
Bild ueber Text steht.
Geprueft: 399 Tests unter PHP 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss auf allen vier Darstellungen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut wollte mehrere Designs zur Auswahl. Bei Seiten waere das falsch -
dort weiss vorher niemand, was daraus wird. Bei Beitraegen ist die Form
fast immer dieselbe, und wer jedes Mal von vorn anfaengt, baut jedes Mal
etwas leicht anderes.
Bildkopf (Bild ueber die volle Breite, Titel darauf), Klassisch (Titel und
Vorspann schmal, Bild darunter) und Magazin (Titel neben den Eckdaten).
Gewaehlt wird beim Anlegen; danach ist jeder Block aenderbar wie jeder
andere. Kein Aufbau verlaesst sich auf ein Bild, das es noch nicht gibt.
Dabei ist eine Regel gefallen: der erste Textblock eines Abschnitts wurde
automatisch zur Kleinbeschriftung in Akzentfarbe. Das gestaltet nach
Position statt nach Absicht - im Fussbereich wurde daraus roter
Versalientext, in der Magazin-Vorlage aus dem Einstiegsabsatz eine
Beschriftung. Wer eine Beschriftung will, stellt sie ein.
Geprueft: 398 Tests unter PHP 8.2 und 8.4, Editortest, Browserpruefung,
axe ohne Verstoss auf allen drei Aufbauten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut: "Beitragsseite gefaellt mir nicht, sollte wie bei kant-it.de sein
oder besser. Falls noetig Plugin bauen."
Die Originalseite hat Galerie, Vor/Zurueck zwischen Projekten und einen
Abschlussaufruf. Galerie und Aufruf gab es laengst - sie waren nur nicht
benutzt. Die Navigation gab es nicht.
Kein Plugin: ein Plugin ist hier ein Bausteintyp, dessen Eigenschaften der
Kern aufbewahrt, aber nicht rendert - es koennte also nichts ausgeben,
solange es keine Laufzeit dafuer gibt. Und die Nachbarn eines Beitrags
stehen in derselben Tabelle wie die Beitragsliste, die der Kern schon
fuellt. Also ein Baustein: postnav.
Gespeichert sind zwei Beschriftungen, mehr nicht - welcher Beitrag vorher
und nachher kommt, entscheidet der Renderer. Gesucht wird innerhalb
derselben Kategorie, und am Rand wird nicht umgebrochen.
Ausserdem: die uebernommenen Projekttexte begannen mit dem wiederholten
Titel (20 Beitraege), und schmale Abschnitte standen links statt mittig -
max-width ohne margin:auto ist eine halbe Einstellung.
Geprueft: 396 Tests unter PHP 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss auf der Beitragsseite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Wuensche, zwei Bausteine.
Die Autorotation ist Sekunden, kein Haekchen: 0 heisst aus, ab 3 laeuft
sie. Sie braucht ein Skript - das zweite auf einer oeffentlichen Seite
nach den Tabs - und es kommt nur auf Seiten mit einem solchen Slider,
sonst bleibt script-src 'none'. Es laeuft nicht bei "weniger Bewegung",
hoert beim ersten Tippen, Klicken, Wischen oder Scrollen endgueltig auf
und pausiert bei Zeiger, Fokus, Hintergrundtab und ausserhalb des Bildes.
Ohne Skript bleibt der Slider vollstaendig bedienbar.
Die Beitragsliste kann blaettern statt abzuschneiden. Eine Auswahl auf der
Startseite zeigt die neuesten und hoert auf; eine Uebersichtsseite
blaettert und sagt, wie viele es sind. Das war die eigene Regel aus
Arbeitspaket M, die fuer diesen Baustein noch nicht galt.
Beim Bauen zweimal dieselbe Falle wie vorgestern: eine Variable war in der
Schliessung nicht gebunden. Einmal das Sprungziel des Sliders (-78 statt
666), einmal die Seitenzahl - die zweite fiel sofort auf, weil die Seite
mit 500 antwortete.
Geprueft: 395 Tests unter PHP 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>