pruef-struktur: von zehn Fehlern auf null -- vier davon waren keine

Die dritte und letzte rote Pruefung. Sie meldete zehn Fehler, und
sechs davon waren Fehlalarm.

1. SECHS SEITEN "VON NIRGENDWO VERLINKT"

Gemeldet wurden crew-index, entwicklung, rechte, talente, teamlage und
treff-moderation. Keine davon war verwaist -- man kommt auf jede, indem
man eine KACHEL antippt. Die Kacheln stehen im Server (`ziel:
"talente.html"`), und der wurde nie durchsucht. Der blinde Fleck lag in
der Pruefung.

Abgeleitet statt nachgetragen: Statt die sechs von Hand auszunehmen,
kommen workspace.js und crew-adresse.js als Quellen dazu. Wer morgen
eine Kachel anlegt, ist damit automatisch abgedeckt.

2. FALSCHE ZEILENNUMMERN

Die Ortszeit-Pruefung schnitt Blockkommentare heraus und ersetzte sie
durch EIN Leerzeichen -- ab da zaehlte split("\n") falsch. Gemeldet
wurde "pruef-eskalation.mjs:43", dort steht eine Zeile ueber Kekse. Wer
dem nachgeht, findet nichts, haelt die Pruefung fuer kaputt und sieht
beim naechsten Mal nicht mehr nach.

Mit richtigen Zeilen waren sechs der sieben Funde echt: Sie bauen ihr
Tagesdatum aus toISOString(), also aus UTC. Zwischen Mitternacht und
zwei Uhr liefert das den Vortag -- die Pruefungen waeren tagsueber
gruen und nachts rot gewesen.

Der siebte (pruef-eskalation.mjs:70) rechnet von einem FESTEN Mittag
aus und ist damit sicher; das Muster kann es nur nicht unterscheiden.
Auch umgestellt, damit es nicht beim naechsten Lesen wieder auffaellt.

Neu: server/helfer-tag.mjs -- heuteLokal, tagLokal, tagVon. Nicht aus
workspace.js importiert, weil eine Pruefung, die nur ein Datum
braucht, dafuer keinen Server hochfahren soll. pruef-backstage-import
hatte die Rechnung sogar schon richtig stehen -- und benutzte sie 1270
Zeilen weiter unten trotzdem nicht.

3. TOTES CSS

`.rolle__zeichen .nase` war tot. Beim Nachsehen: `.auge` und `.hell`
daneben auch -- sie wurden nur davon verdeckt, dass die Pruefung den
Klassennamen als TEILZEICHENKETTE gegen den Quelltext haelt, und
"auge" steckt in "Auge", "hell" in "hell". Dutzende Treffer im
Fliesstext deutscher Kommentare. `.fuell` bleibt, die wird benutzt.
Die Schwaeche der Suche steht jetzt an der Stelle notiert.

4. start.css MIT 347 KB

Die Grenze soll verhindern, "dass eine Seite unnoetig viel laedt". Auf
der echten Seite nachgemessen:

  start.css        auf der Platte 346 KB   uebertragen 110 KB (br)
  chat.css                         59 KB                16 KB
  entwicklung.css                  33 KB                 8 KB

Caddy packt unterwegs. Von start.css sind ausserdem 227 KB KOMMENTAR
(64 %) -- die Pruefung bestrafte genau das, was dieses Haus absichtlich
tut, und haette eine Datei mit 199 KB dichtem CSS durchgewunken.
Dasselbe Argument steht drei Absaetze hoeher schon fuer gestufte
Bilder.

Jetzt zwei Zahlen, weil es zwei Fragen sind: was der BESUCHER laedt
(brotli Stufe 4 -- bei 4 liefert node 111 KB, Caddy 110, jede andere
Stufe waere eine erfundene Zahl) und was ein MENSCH pflegen muss (ohne
Kommentare). Sonst koennte man die erste Zahl klein halten, indem man
immer mehr Prosa schreibt.

MIT GEGENPROBE, in drei Faellen: 281 KB dichtes CSS faellt durch,
404 KB Kommentar gehen durch, und 246 KB sich WIEDERHOLENDES CSS
faellt ebenfalls durch -- sonst koennte man die erste Grenze mit
Wiederholung unterlaufen.

  pruef-struktur          0 Fehler (vorher 10)
  pruef-eskalation 40, pruef-modi-livecheck 16, pruef-treff-werkzeuge 70,
  pruef-backstage-import 160, pruef-css-klassen, pruef-crew-adresse 132
  -- alle 0 Fehler

Damit sind alle drei roten Pruefungen gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-16 18:03:50 +02:00
co-authored by Claude Opus 5
parent 0852723493
commit 12d4d48975
37 changed files with 580 additions and 385 deletions
+127 -2
View File
@@ -30,6 +30,7 @@
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
import { join, dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { brotliCompressSync, constants } from "node:zlib";
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO = join(__dirname, "..");
@@ -183,8 +184,33 @@ ok(nurPraefix > 0, `${nurPraefix} zusammengesetzte Aufrufe: nur Praefix pruefbar
/* ---------- 4. Jede Seite ist erreichbar --------------------------------- */
console.log(`\n=== Erreichbarkeit ===`);
/* WER AUF EINE SEITE ZEIGT, IST NICHT NUR EIN href (16.09.2026).
Diese Zeile meldete sechs Seiten als "von nirgendwo verlinkt":
crew-index, entwicklung, rechte, talente, teamlage und
treff-moderation. Keine davon war verwaist -- man kommt auf jede,
indem man eine KACHEL antippt. Die Kacheln stehen aber im Server
(`ziel: "talente.html"`), und der wurde hier nie durchsucht.
Eine Pruefung, die seit Wochen sechs Fehler meldet, die keine sind,
ist schlimmer als keine: Man liest sie nicht mehr, und dann uebersieht
man die siebte, die echt ist. Der blinde Fleck lag in der Pruefung,
nicht im Haus.
ABGELEITET, NICHT NACHGETRAGEN: Statt die sechs von Hand
auszunehmen, kommen die Dateien dazu, in denen die Ziele wirklich
stehen. Wer morgen eine Kachel anlegt, ist damit automatisch
abgedeckt -- und wer eine Seite anlegt, auf die nichts zeigt, faellt
weiterhin auf. */
const zeigerQuellen = [
...quellenMitLinks,
/* Die Kachellisten -- dort steht `ziel: "talente.html"`. */
{ name: "server/workspace.js", pfad: join(SERVER, "workspace.js") },
/* Die Zugangswaende -- crew-index.html wird dort ausgeliefert. */
{ name: "server/crew-adresse.js", pfad: join(SERVER, "crew-adresse.js") },
];
const verlinkt = new Set(["index.html", "start.html"]);
for (const { pfad: quelle } of quellenMitLinks) {
for (const { pfad: quelle } of zeigerQuellen) {
const text = readFileSync(quelle, "utf8");
for (const m of text.matchAll(/["'`](?:\/workspace\/)?([a-z0-9-]+\.html)/g)) {
verlinkt.add(m[1]);
@@ -239,6 +265,23 @@ ok(reste.length === 0, reste.length ? reste.join(" · ") : "keine Entwicklungsre
/* ---------- 7. Groesse der Assets ---------------------------------------- */
console.log(`\n=== Dateigroessen ===`);
/** Ist diese Text-Datei zu schwer? Gibt den Grund oder `null`.
*
* ALS EIGENE FUNKTION, damit die Gegenprobe sie mit erfundenem Inhalt
* aufrufen kann. Eine Messung, die nur ueber die echten Dateien
* laeuft, ist genau so lange gruen, wie zufaellig keine zu gross ist
* -- und beweist nie, dass sie ueberhaupt anschlagen kann. */
function textZuSchwer(roh) {
const gepackt = Math.round(brotliCompressSync(roh, {
params: { [constants.BROTLI_PARAM_QUALITY]: 4 },
}).length / 1024);
if (gepackt > 200) return `${gepackt} KB uebertragen`;
const ohneKommentar = Math.round(
roh.toString("utf8").replace(/\/\*[\s\S]*?\*\//g, "").length / 1024);
if (ohneKommentar > 200) return `${ohneKommentar} KB ohne Kommentare`;
return null;
}
let schwer = [];
for (const unter of ["assets/js", "assets/css", "assets/img"]) {
const ordner = join(WS, unter);
@@ -300,11 +343,81 @@ for (const unter of ["assets/js", "assets/css", "assets/img"]) {
}
continue;
}
/* BEI TEXT MISST DIE ROHE GROESSE DAS FALSCHE (16.09.2026).
Die Grenze soll verhindern, "dass eine Seite unnoetig viel
laedt" -- so steht es oben. Fuer Bilder ist die Datei genau das,
was geladen wird. Fuer CSS und JS nicht: Caddy komprimiert sie
unterwegs. Nachgemessen auf der echten Seite am 16.09.:
start.css auf der Platte 346 KB uebertragen 110 KB (br)
chat.css 59 KB 16 KB
entwicklung.css 33 KB 8 KB
Die Pruefung meldete start.css mit 347 KB -- davon sind 227 KB
KOMMENTAR (64 %). Sie bestrafte damit genau das, was dieses Haus
absichtlich tut: jede Entscheidung danebenschreiben. Und sie
haette eine Datei mit 199 KB dichtem, unkommentiertem CSS
durchgewunken.
Dasselbe Argument steht drei Absaetze weiter oben schon einmal,
fuer die gestuften Bilder: "Bei einem Bild, das in mehreren
Groessen vorliegt, misst sie das Falsche."
JETZT ZWEI ZAHLEN, weil es zwei verschiedene Fragen sind:
1. WAS DER BESUCHER LAEDT -- brotli, Stufe 4. Die Stufe ist
nicht geraten: Bei 4 liefert node 111 KB, Caddy liefert
110. Jede andere Stufe waere eine Zahl, die mit der
Wirklichkeit nichts zu tun hat.
2. WAS EIN MENSCH PFLEGEN MUSS -- die Datei ohne Kommentare.
Sonst koennte man die erste Zahl klein halten, indem man
immer mehr Prosa schreibt, und niemand merkt, dass das CSS
selbst ausgeufert ist. */
if (/\.(css|js)$/.test(n)) {
const grund = textZuSchwer(readFileSync(p));
if (grund) schwer.push(`${unter}/${n} ${grund}`);
continue;
}
if (kb > 200) schwer.push(`${unter}/${n} ${kb} KB`);
}
}
ok(schwer.length === 0, schwer.length ? `ueber 200 KB: ${schwer.join(", ")}` : "keine Datei ueber 200 KB");
/* GEGENPROBE ZUR GROESSENMESSUNG. Sie ist seit dem 16.09.2026 kein
blosser Blick auf die Dateigroesse mehr, sondern packt den Inhalt
zusammen -- und eine Messung, die niemand je hat anschlagen sehen,
ist eine Behauptung. Drei Faelle, und jeder muss anders ausgehen. */
{
/* 1. Viel echtes CSS, das sich nicht wegpacken laesst. */
let dicht = "";
for (let i = 0; i < 9000; i++) {
dicht += `.k${i.toString(36)}-${(i * 7919).toString(36)} { color: #${
(i * 2654435761 % 16777215).toString(16).padStart(6, "0")}; }\n`;
}
ok(textZuSchwer(Buffer.from(dicht)) !== null,
`dichtes CSS von ${Math.round(dicht.length / 1024)} KB faellt durch `
+ `(${textZuSchwer(Buffer.from(dicht))})`);
/* 2. Dieselbe Menge Text, aber als Prosa im Kommentar -- die packt
sich auf ein Zehntel und darf durchgehen. Das ist der ganze
Grund fuer die Umstellung. */
const prosa = "/* " + "Jede Entscheidung gehoert danebengeschrieben. ".repeat(9000) + " */\n"
+ ".a { color: #fff; }\n";
ok(textZuSchwer(Buffer.from(prosa)) === null,
`${Math.round(prosa.length / 1024)} KB Kommentar gehen durch — sie kosten `
+ "den Besucher fast nichts");
/* 3. UND DIE ZWEITE ZAHL MUSS AUCH ANSCHLAGEN: viel echtes CSS, das
sich zwar gut packt (weil es sich wiederholt), aber trotzdem
niemand mehr pflegen kann. Ohne diesen Fall koennte man die
erste Grenze mit Wiederholung unterlaufen. */
const wiederholt = ".gleich { color: #ffffff; padding: 4px; }\n".repeat(6000);
ok(textZuSchwer(Buffer.from(wiederholt)) !== null,
`${Math.round(wiederholt.length / 1024)} KB sich wiederholendes CSS faellt `
+ `ebenfalls durch (${textZuSchwer(Buffer.from(wiederholt))})`);
}
/* ---------- 8. Totes CSS ------------------------------------------------- */
/* start.css wird auf JEDER Seite geladen und ist 126 KB gross. Was darin
@@ -611,7 +724,19 @@ console.log(`\n=== Rechnen die Pruefungen mit Ortszeit? ===`);
anzeigen -- eine Warnung, die immer kommt, und die liest bald
niemand mehr. */
if (datei === "pruef-struktur.mjs") continue;
const text = readFileSync(join(SERVER, datei), "utf8").replace(/\/\*[\s\S]*?\*\//g, " ");
/* KOMMENTARE RAUS, ZEILEN BEHALTEN (16.09.2026).
Hier stand `replace(…, " ")` -- ein Blockkommentar ueber zwanzig
Zeilen wurde also zu einem Leerzeichen, und ab da zaehlte
`split("\n").length` falsch. Gemeldet wurden Zeilennummern, die
auf voellig anderen Code zeigten: pruef-eskalation.mjs:43 ist
eine Zeile ueber Kekse.
Wer einer solchen Meldung nachgeht, findet dort nichts, haelt
die Pruefung fuer kaputt und sieht beim naechsten Mal gar nicht
mehr nach. Jetzt bleibt je Zeile eine Zeile stehen. */
const text = readFileSync(join(SERVER, datei), "utf8")
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "));
/* Das Muster ist eng gefasst: NUR toISOString() gefolgt von einem
Schnitt auf zehn Zeichen -- also genau die Bildung eines
Tagesdatums. `new Date().toISOString()` allein (ein voller