/* STEHT DAS CSS AUCH AUF DER SEITE, DIE ES BENUTZT? ENTSTANDEN AUS EINEM ECHTEN FEHLER (05.09.2026). Die Teilnehmerwahl im Kalender benutzte `.wahl__knopf` und `.wahl__rolle`. Beide sind sauber definiert -- in dateien.css. Und kalender.html bindet dateien.css nicht ein. Ohne `display:inline-flex` und `gap` stehen zwei eben direkt nebeneinander. Auf Filipes Bildschirm stand deshalb: DogfatherDogFather VanVanDogFather CigdemManager Vierzig Prüfungen zur Teilnehmerwahl waren grün. Sie prüften, dass die richtigen Personen ankommen, dass Fremde weggefiltert werden, dass die Sichtbarkeit stimmt -- alles richtig, und alles am Problem vorbei. KEINE hat gefragt, ob das Ergebnis auch aussieht wie gedacht. Das ist die dritte Sorte Fehler aus der Projektnotiz: nicht der übersprungene Test, nicht der grüne, der das Falsche prüft, sondern die Frage, die niemand gestellt hat. WAS HIER GEMESSEN WIRD Für jede Workspace-Seite: 1. Welche Stylesheets bindet sie ein? (rekursiv über @import) 2. Welche Klassen vergibt sie -- im HTML *und* in ihrem eigenen JavaScript (className, classList.add, setAttribute('class')). 3. Ist jede davon in einem der eingebundenen Stylesheets definiert? WAS BEWUSST NICHT GEMESSEN WIRD Klassen, die es nur zum Anfassen gibt (`js-`), reine Datenanker und Klassen aus gemeinsamen Bausteinen, die jede Seite lädt. Und die Gegenrichtung ("CSS ohne Benutzer") gehört nicht hierher: Ungenutztes CSS ist Ballast, kein Fehler. DRITTER AUSGANG: Findet die Prüfung keine Seiten oder keine Klassen, ist das kein "bestanden" -- dann hat sie nichts gemessen und sagt es. GEGENPROBE am Ende: Eine erfundene Klasse muss auffallen, und eine echte darf es nicht. */ import { readFileSync, readdirSync, existsSync } from "node:fs"; import { join, dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const HIER = dirname(fileURLToPath(import.meta.url)); const WURZEL = resolve(HIER, ".."); const WS = join(WURZEL, "workspace"); let fehler = 0; const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; /* ---------- Welche Stylesheets bindet eine Seite ein? -------------------- */ /** Liest ein Stylesheet und folgt seinen @import-Zeilen. */ function cssMitImporten(pfad, gesehen = new Set()) { const echt = resolve(pfad); if (gesehen.has(echt) || !existsSync(echt)) return ""; gesehen.add(echt); const text = readFileSync(echt, "utf8"); let alles = text; for (const m of text.matchAll(/@import\s+(?:url\()?["']([^"')]+)["']\)?/g)) { alles += "\n" + cssMitImporten(join(dirname(echt), m[1].split("?")[0]), gesehen); } return alles; } /** Alle Klassennamen, die ein CSS-Text definiert. */ function definierteKlassen(css) { const raus = new Set(); /* Erst Kommentare weg: In diesem Haus stehen lange Erklärungen im CSS, und dort kommen Klassennamen als Fließtext vor ("wie .wahl__knopf in dateien.css"). Die zählen nicht als Definition -- sonst würde ausgerechnet der Kommentar, der den Fehler beschreibt, ihn verdecken. */ const ohne = css.replace(/\/\*[\s\S]*?\*\//g, " "); for (const m of ohne.matchAll(/\.(-?[A-Za-z_][\w-]*)/g)) raus.add(m[1]); return raus; } /* ---------- Welche Klassen vergibt eine Seite? --------------------------- */ /** Klassen aus dem HTML. */ function klassenAusHtml(html) { const raus = new Set(); const ohneKommentar = html.replace(//g, " "); for (const m of ohneKommentar.matchAll(/\bclass\s*=\s*["']([^"']+)["']/g)) { for (const k of m[1].split(/\s+/)) if (k) raus.add(k); } return raus; } /** Klassen aus JavaScript. Nur die Formen, die dieses Haus benutzt -- * ein vollständiger Parser wäre hier Aufwand ohne Ertrag. */ function klassenAusJs(js) { const raus = new Set(); const ohne = js.replace(/\/\*[\s\S]*?\*\//g, " "); const muster = [ /\.className\s*=\s*['"]([^'"]+)['"]/g, /setAttribute\(\s*['"]class['"]\s*,\s*['"]([^'"]+)['"]/g, /class\s*=\s*\\?["']([^"'\\]+)/g, // in Template-Literalen ]; for (const m of muster) { for (const t of ohne.matchAll(m)) { for (const stueck of t[1].split(/['",\s]+/)) { const k = stueck.trim(); /* Nur echte Klassennamen -- keine Variablen, keine Ausdrücke. */ if (k && /^-?[A-Za-z_][\w-]*$/.test(k)) raus.add(k); } } } /* classList.add() BRAUCHT eine eigene Behandlung. Der erste Entwurf nahm den ganzen Klammerinhalt und zerlegte ihn an Anführungszeichen und Kommas. Bei classList.add(i === 0 ? 'marke__haupt' : 'marke__leise') fiel dabei `i` als angebliche Klasse heraus -- und die Prüfung meldete auf sechzehn Seiten eine fehlende Klasse `.i`, die es nie gab. Eine Warnung, die falsch ist, ist schlimmer als keine: Sie kostet Zeit und wird beim nächsten Mal überlesen. Deshalb werden hier NUR die Zeichenketten gelesen, nie der Ausdruck drumherum. */ for (const t of ohne.matchAll(/classList\.(?:add|remove|toggle)\(([^)]*)\)/g)) { for (const s of t[1].matchAll(/['"]([^'"]+)['"]/g)) { for (const k of s[1].trim().split(/\s+/)) { if (k && /^-?[A-Za-z_][\w-]*$/.test(k)) raus.add(k); } } } return raus; } /** Welche eigenen Skripte bindet die Seite ein? */ function skripteVon(html) { const raus = []; for (const m of html.matchAll(/]+src\s*=\s*["']([^"']+)["']/g)) { const p = m[1].split("?")[0]; if (/^https?:/.test(p)) continue; // fremde Skripte nicht raus.push(join(WS, p.replace(/^\//, "").replace(/^workspace\//, ""))); } return raus; } function stylesheetsVon(html) { const raus = []; for (const m of html.matchAll(/]+rel\s*=\s*["']stylesheet["'][^>]*>/g)) { const h = /href\s*=\s*["']([^"']+)["']/.exec(m[0]); if (!h || /^https?:/.test(h[1])) continue; raus.push(join(WS, h[1].split("?")[0].replace(/^\//, "").replace(/^workspace\//, ""))); } return raus; } /* Klassen, die absichtlich kein CSS haben. Jede Ausnahme braucht einen Grund -- eine Liste ohne Begründung wächst, bis sie alles enthält. */ const OHNE_CSS = new Set([ "leise", // Grundstil, steht in gate.css als Elementregel ]); const OHNE_CSS_PRAEFIX = ["js-", "u-"]; // reine Anfasser, nie gestaltet /* ======================================================================= Die Messung ======================================================================= */ console.log("\n=== Ist jede benutzte Klasse auf ihrer Seite auch definiert? ==="); const seiten = readdirSync(WS).filter((n) => n.endsWith(".html")); /* DIE ANZAHL GEHOERT IN DIE BEDINGUNG, nicht nur in den Meldetext. Findet die Prüfung keine Seiten, hat sie nichts gemessen -- und "0 von 0 Klassen fehlen" wäre grün und wertlos. */ ok(seiten.length >= 10, `${seiten.length} Workspace-Seiten gefunden`); /* ALLES CSS DES HAUSES -- für die entscheidende Unterscheidung. Eine Klasse ohne CSS ist zweierlei, und nur eines davon ist ein Fehler: (a) NIRGENDS gestaltet. Ein reiner Struktur-Anker wie `.k-wdh__ende`, der nur ein
benennt. Kein Fehler -- sonst müsste man jedem Container Deko andichten. (b) Gestaltet, aber in einer Datei, die DIESE SEITE NICHT LÄDT. Das ist der Fehler von heute: Jemand hat es gestaltet, es sieht im Editor richtig aus, und auf der Seite kommt nichts an. Niemandem fällt es auf, weil das CSS ja existiert. Nur (b) schlägt an. */ const ALLE_CSS = definierteKlassen( readdirSync(join(WS, "assets/css")).filter((n) => n.endsWith(".css")) .map((n) => readFileSync(join(WS, "assets/css", n), "utf8")).join("\n")); let geprueft = 0; const luecken = []; // (b) -- gestaltet, aber nicht hier const ungestaltet = []; // (a) -- nur zur Ansicht for (const seite of seiten) { const html = readFileSync(join(WS, seite), "utf8"); /* DIE SEITE KANN IHRE STILE AUCH SELBST TRAGEN (ergaenzt 19.09.2026). Bis heute las diese Pruefung nur die verlinkten .css-Dateien. Ein