Vorschau zeigt nie eine Seite ohne ihre Gestaltung
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

Gemessen: die Farben in der Vorschau und auf der veroeffentlichten
Seite waren immer identisch. Der Aufbau war das Problem - die
erzeugten Stile wurden im load-Ereignis des Rahmens angewandt, und das
feuert erst, wenn das letzte Bild geladen ist. Auf der nachgebauten
Startseite stand die Vorschau dadurch 1440 ms lang ohne eine einzige
Farbe da.

Jetzt wird angewandt, sobald das Dokument geparst ist; der Rahmen
bleibt bis dahin verdeckt; und gestaltet wird nur das neue Dokument -
der erste Anlauf gestaltete das alte und schaltete zu frueh sichtbar.

Die Editorkontrolle prueft dagegen das Verhalten statt der Zeit: der
Rahmen muss beim Aufbau verdeckt werden und danach wieder sichtbar.
Eine Zeitmessung haette auf der kleinen Testseite nichts gefunden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-13 16:08:12 +02:00
1 parent e0d06ac44c
commit 6c477a52be
4 files changed
+88 -2

No files matched your search

+16
View File
@@ -466,6 +466,22 @@ Drei Entscheidungen, die nicht offensichtlich sind:
Der Preis steht in der Verwaltung: das Widget braucht JavaScript. Ohne Skript ist das Formular dann nicht absendbar – genau die Art von Verschlechterung, die dieses Projekt sonst vermeidet. Deshalb steht im Formular ein `<noscript>`-Hinweis mit dem Weg über E-Mail.
## ADR 061 – Die Vorschau zeigt nie eine Seite ohne ihre Gestaltung (13.09.2026)
Umut, mit Blick auf einen Screenshot des Editors: „im Screenshot sind die Buttons nicht gleiche Farbe, es sollte exakt nachladen, die Vorschau soll wie das echte aussehen."
**Nachgemessen: die Farben sind identisch.** Knopf, Hero-Hintergrund, Hintergrundbild und Textfarbe geben in der Vorschau und auf der veröffentlichten Seite exakt dieselben berechneten Werte. Was auf dem Bild zu sehen war, war ein Zwischenzustand – links oben stand „Vorschau wird aktualisiert …".
Der Zwischenzustand war trotzdem ein Fehler, und ein langer: **1440 Millisekunden** lang war die Vorschau sichtbar und ungestaltet, gemessen an der nachgebauten Startseite. Der Grund liegt in einer Zeile: die erzeugten Stile wurden im `load`-Ereignis des Rahmens angewandt – und das feuert erst, wenn **das letzte Bild** geladen ist. Bei sechzehn Bildern sieht man bis dahin die Seite ohne eine einzige Farbe.
Drei Änderungen, jede aus einem eigenen Fehlversuch:
1. **Angewandt wird, sobald das Dokument geparst ist**, nicht wenn es fertig geladen ist. Für `srcdoc` gibt es dafür kein verlässliches Ereignis, also wird Bild für Bild nachgesehen, ob der Kopf schon da ist.
2. **Der Rahmen bleibt verdeckt, bis die Gestaltung sitzt.** Eine leere Fläche für einen Moment ist besser als eine falsche Seite.
3. **Nur das neue Dokument wird gestaltet.** Der erste Anlauf sah beim Warten das *alte* Dokument – es trägt dieselben Kennzeichen –, gestaltete es, schaltete sichtbar, und `srcdoc` ersetzte es gleich darauf durch den ungestalteten neuen Inhalt. Der Fehler war damit derselbe wie vorher, nur schneller.
Gegen einen Rückfall prüft die Editorkontrolle nicht die Zeit, sondern das Verhalten: der Rahmen **muss** beim Aufbau verdeckt werden und danach wieder sichtbar. Eine Zeitmessung hätte auf der kleinen Testseite nichts gefunden – das Fenster ist dort zu kurz.
## ADR 060 – Die Fehlerseite eines Formulars ist dieselbe Seite (13.09.2026)
Beim Einbau von Turnstile gefunden: wer ein Pflichtfeld vergaß, bekam die Seite **ohne Gestaltung** zurück. Der Formularcode baute sie selbst ein zweites Mal auf und ließ dabei alles weg, was er nicht kannte – Theme, Blockstile, Schriften, das Stylesheet-Bündel. Zwei Renderwege für dieselbe Seite, und der zweite hinkte hinterher, seit es den ersten gibt.
+1
View File
@@ -34,6 +34,7 @@ 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.
- **Behoben: die Vorschau zeigte beim Aufbau bis zu 1440 ms lang eine Seite ohne Gestaltung** (ADR 061). Die Stile wurden erst angewandt, wenn das letzte Bild geladen war. Jetzt sitzen sie, sobald das Dokument geparst ist, und der Rahmen bleibt bis dahin verdeckt. Die Farben selbst stimmten immer – Vorschau und veröffentlichte Seite geben nachweislich dieselben berechneten Werte.
- **Cloudflare Turnstile für Formulare, optional** (ADR 059): aus, bis in den Einstellungen beide Schlüssel stehen; dann erscheint die Prüfung in jedem Formular, und die Sicherheitsrichtlinie öffnet sich nur auf Seiten mit Formular für `challenges.cloudflare.com`. Antwortet Cloudflare nicht, wird die Anfrage trotzdem angenommen. Die eingebauten drei Prüfungen ohne Dritte bleiben unabhängig davon bestehen. Der Preis steht in der Verwaltung dabei: das Widget braucht JavaScript.
- **Behoben: die Fehlerseite eines Formulars hatte keine Gestaltung** (ADR 060). Wer ein Pflichtfeld vergaß, bekam eine nackte Seite – der Formularcode baute sie selbst auf und ließ Theme, Blockstile und Schriften weg. Jetzt rendert derselbe Controller wie sonst auch.
- **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`.
+35 -2
View File
@@ -563,9 +563,20 @@ async function updatePreview() {
if (sequence !== previewSequence) return;
const frame = $('#preview');
const top = frame.contentWindow?.scrollY || 0;
frame.onload = () => {
if (sequence !== previewSequence) return;
let angewandt = false;
// Die Gestaltung muss sitzen, sobald der Kopf des Dokuments geparst ist. Vorher hing das an
// frame.onload - und das feuert erst, wenn das letzte Bild geladen ist. Auf einer Seite mit
// sechzehn Bildern stand die Vorschau dadurch sekundenlang ohne jede Gestaltung da: schwarze
// Knöpfe statt roter, kein Hero, keine Farben. Genau das hat Umut im Bild gesehen.
// Welches Dokument gerade im Rahmen steht. Ohne diesen Vergleich wird beim Warten auf das
// neue Dokument das alte gestaltet - es hat dieselben Kennzeichen -, der Rahmen wird sichtbar
// geschaltet, und gleich darauf ersetzt srcdoc den Inhalt durch den ungestalteten neuen.
const altesDokument = frame.contentDocument;
const anwenden = () => {
if (angewandt || sequence !== previewSequence) return;
const doc = frame.contentDocument;
if (!doc || doc === altesDokument || !doc.getElementById('kant-styles')) return;
angewandt = true;
const style = doc.createElement('link'); style.rel = 'stylesheet'; style.href = '/assets/preview.css'; doc.head.append(style);
// The preview delivers the generated stylesheet as inert template text (see page.html.twig).
// Applying it as a constructed stylesheet is not a source in the sense of the policy, so the
@@ -590,9 +601,31 @@ async function updatePreview() {
});
doc.addEventListener('submit', event => event.preventDefault());
frame.contentWindow.scrollTo(0, top); highlight();
// Erst jetzt ansehen lassen: bis hierher zeigte das Fenster eine Seite ohne Gestaltung.
frame.style.visibility = '';
$('#preview-status').textContent = 'Entwurf · Zum Auswählen direkt auf ein Element klicken.';
};
// Solange die Gestaltung fehlt, bleibt die Fläche leer statt falsch.
frame.style.visibility = 'hidden';
frame.srcdoc = html;
// Sobald der Kopf da ist, wird gestaltet - geprüft Bild für Bild statt auf ein Ereignis zu
// warten, das es für srcdoc nicht zuverlässig gibt.
const warten = () => {
if (angewandt || sequence !== previewSequence) return;
anwenden();
if (!angewandt) requestAnimationFrame(warten);
};
requestAnimationFrame(warten);
// Das Netz darunter ist das Ladeereignis - und zwar ohne Uhr: bringt auch das fertig geladene
// Dokument keine Gestaltung mit, stimmt etwas anderes nicht, und dann ist eine unfertige
// Vorschau immer noch besser als eine unsichtbare.
frame.onload = () => {
anwenden();
if (!angewandt) {
frame.style.visibility = '';
$('#preview-status').textContent = 'Vorschau ohne Gestaltung - bitte die Seite neu laden.';
}
};
} catch (error) {
if (error.name === 'AbortError' || sequence !== previewSequence) return;
$('#preview-status').textContent = `Letzte gültige Vorschau: ${error.message}`;
+36
View File
@@ -75,6 +75,42 @@ try {
await page.getByRole('button', { name: 'Wiederholen', exact: true }).click();
await frame.getByRole('heading', { name: 'Meine bearbeitete Website', exact: true }).waitFor();
checks.push({ name: 'preview selection, property edit, undo and redo', passed: true });
// Die Vorschau darf nie sichtbar sein, solange ihre Gestaltung fehlt. Vorher hing das Anwenden
// am Ladeereignis des Rahmens - und das feuert erst, wenn das letzte Bild geladen ist. Auf der
// nachgebauten Startseite von kant-it.de stand die Vorschau dadurch 1440 ms lang mit falschen
// Farben da: schwarze Knöpfe statt roter.
const beobachtung = page.evaluate(async () => {
const rahmen = document.getElementById('preview');
const verlauf = [];
let ungestylt = 0;
// Zweierlei wird festgehalten: dass der Rahmen ueberhaupt verdeckt wird, waehrend das neue
// Dokument entsteht - und ob er je sichtbar war, ohne seine Gestaltung zu haben.
new MutationObserver(() => verlauf.push(rahmen.style.visibility || 'sichtbar'))
.observe(rahmen, { attributes: true, attributeFilter: ['style'] });
const bis = performance.now() + 5000;
while (performance.now() < bis) {
const doc = rahmen.contentDocument;
if ('visible' === getComputedStyle(rahmen).visibility
&& doc?.getElementById('kant-styles')
&& 0 === (doc.adoptedStyleSheets ?? []).length) {
ungestylt++;
}
await new Promise(r => setTimeout(r, 30));
}
return { verlauf, ungestylt };
});
await page.click('#duplicate');
const { verlauf, ungestylt } = await beobachtung;
assert.equal(ungestylt, 0, 'Die Vorschau war sichtbar, bevor ihre Gestaltung angewandt war.');
// Der Rahmen muss verdeckt worden und danach wieder sichtbar geworden sein. Ohne diese Prüfung
// hinge der Nachweis am Zufall: auf einer kleinen Seite ist das Fenster zu kurz, um es zu
// treffen - auf der nachgebauten Startseite von kant-it.de war es 1440 ms lang.
assert.ok(verlauf.includes('hidden'), 'Die Vorschau wird beim Aufbau nicht verdeckt.');
assert.equal(verlauf[verlauf.length - 1], 'sichtbar', 'Die Vorschau bleibt verdeckt.');
await page.click('#undo');
await page.locator('#preview-status').filter({ hasText: 'Zum Auswählen' }).waitFor();
checks.push({ name: 'preview never shows unstyled content', passed: true });
console.log('Preview edits and history passed.');
await page.getByRole('button', { name: 'Einklappen', exact: true }).click();