Fassung 1.4.3: Update ohne Git, Verwaltung auf dem Telefon, eigene Symbole
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

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>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-13 23:01:32 +02:00
1 parent ea3dd4ddf2
commit d6d476177a
26 files changed
+1740 -26

No files matched your search

+89
View File
@@ -1077,6 +1077,30 @@ final class CmsTest extends TestCase
}
}
public function testTheAdministrationDrawsItsOwnIconsInsteadOfBorrowingCharacters(): void
{
// Umut: „Machst du auch gleich eigene Icons statt die Web-Dinger zu nutzen – auf dem
// Smartphone kommen sonst Emojis.“ Genau so ist es: ein Telefon ersetzt ein Zeichen wie
// das Zahnrad durch ein buntes Emoji in fremdem Stil, und die Navigation sieht aus wie
// zusammengewürfelt. Eigene Pfade sind eine Linienstärke, eine Farbe und kein Request.
$shell = (string) file_get_contents(__DIR__.'/../templates/admin/shell.html.twig');
self::assertDoesNotMatchRegularExpression(
'/[\\x{2190}-\\x{2BFF}\\x{1F300}-\\x{1FAFF}]/u',
$shell,
'In der Navigation steht ein Zeichen, das ein Telefon zum Emoji macht.',
);
self::assertStringContainsString('admin/icon.html.twig', $shell);
$icons = (string) file_get_contents(__DIR__.'/../templates/admin/icon.html.twig');
// Jedes verwendete Symbol muss es geben - sonst zeichnet die Vorlage stillschweigend das
// Ersatzsymbol, und zwei Einträge sehen gleich aus.
foreach (['pages', 'partials', 'menus', 'media', 'forms', 'statistics', 'theme', 'settings', 'system', 'users', 'external'] as $name) {
self::assertStringContainsString($name.": 'M", $icons, $name.' fehlt in der Symbolsammlung.');
}
preg_match_all("/'(M[^']+)'/", $icons, $pfade);
self::assertSame(count($pfade[1]), count(array_unique($pfade[1])), 'Zwei Symbole tragen denselben Pfad.');
}
public function testNoStylesheetHasADanglingSelector(): void
{
// Gefunden, als der Einwilligungsdialog plötzlich ungestaltet unter dem Fuß stand: beim
@@ -2913,6 +2937,71 @@ final class CmsTest extends TestCase
self::assertStringNotContainsString('fetchpriority', $second);
}
public function testTheBackgroundOfTheFirstSectionIsFetchedBeforeTheStylesheetFindsIt(): void
{
// Auf der Startseite des Piloten ist das größte Element über der Falz kein `<img>`, sondern
// der Hintergrund des ersten Abschnitts. Ein Hintergrundbild steht nur im Stylesheet, und
// das kommt nach dem HTML - der Browser erfährt also erst vom Bild, wenn er das CSS
// geparst hat. Gemessen: 1,3 s statt 0,9 s bis zum größten Element.
$media = new MediaLibrary($this->db, $this->path.'-media');
$datei = tempnam(sys_get_temp_dir(), 'kant-hero-');
$limit = ini_get('memory_limit');
ini_set('memory_limit', '512M');
try {
$bild = imagecreatetruecolor(2600, 1600);
imagejpeg($bild, $datei, 80);
imagedestroy($bild);
$item = $media->upload(new UploadedFile($datei, 'hero.jpg', test: true));
$version = static fn (Connection $db): int => (int) $db->fetchOne('SELECT version FROM page WHERE id = ?', ['page-1']);
$ohne = DemoContent::starter('Ohne Hintergrund');
$entwurf = $this->pages->saveDraft('page-1', $ohne, $version($this->db));
$this->pages->publish('page-1', $entwurf, $version($this->db));
$sparsam = (int) $this->request('/')->headers->get('X-Kant-Queries');
$mit = DemoContent::starter('Mit Hintergrund');
$mit['schemaVersion'] = max(10, $mit['schemaVersion'] ?? 10);
$mit['blocks'][0]['style']['desktop']['backgroundImage'] = $item['id'];
$entwurf = $this->pages->saveDraft('page-1', $mit, $version($this->db));
$this->pages->publish('page-1', $entwurf, $version($this->db));
$antwort = $this->request('/');
$html = (string) $antwort->getContent();
// Drei Verweise, einer je Gerät - und genau einer trifft zu.
preg_match_all('/<link rel="preload" as="image" href="([^"]+)" media="([^"]+)"[^>]*>/', $html, $treffer, PREG_SET_ORDER);
self::assertCount(3, $treffer, 'Der Hintergrund des ersten Abschnitts wird nicht vorab geholt.');
self::assertSame(
['(min-width:1025px)', '(min-width:768px) and (max-width:1024px)', '(max-width:767px)'],
array_column($treffer, 2),
);
self::assertSame(
['/media/'.$item['id'].'/2400', '/media/'.$item['id'].'/1600', '/media/'.$item['id'].'/960'],
array_column($treffer, 1),
'Jedes Gerät bekommt die Variante, die es auch ohne Vorabholen bekäme.',
);
foreach ($treffer as $verweis) {
// Der Punkt der Übung: Vorabverweis und Regel müssen dieselbe Datei meinen. Sonst
// lädt der Browser zwei Bilder statt einem, und das Vorabholen kostet mehr als es
// bringt.
self::assertStringContainsString('url("'.$verweis[1].'")', $html, 'Der Vorabverweis meint eine andere Datei als die Regel.');
self::assertStringContainsString('fetchpriority="high"', $verweis[0]);
}
// Dasselbe Bild wird während einer Ausgabe mehrfach gesucht: je Gerät eine Regel, dazu
// der Kopf. Gefragt wird trotzdem nur einmal.
$mitBild = (int) $antwort->headers->get('X-Kant-Queries');
self::assertLessThanOrEqual($sparsam + 1, $mitBild, sprintf(
'Ein Hintergrundbild kostet eine Abfrage, nicht vier (%d statt %d).',
$mitBild,
$sparsam,
));
} finally {
@unlink($datei);
ini_set('memory_limit', (string) $limit);
}
}
public function testAPageDoesNotAskTheDatabaseOverAndOver(): void
{
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', DemoContent::pages()['home'], 0), 1);