Der Waechter gegen Namensstreit gilt jetzt fuer das ganze Haus

Er wurde gestern fuer `reaktion.css` gebaut, nachdem `.stufen` dort
zweimal stand und die Stufenleiter der Spenden dadurch das Aussehen
der Chat-Leiste bekam -- drei Stufen nebeneinander, 572 Pixel in
einer 327 Pixel schmalen Spalte, mit der waagerechten Rollleiste auf
Filipes Bildschirmfoto.

Eine Wache, die nur eine Datei ansieht, findet genau die Fehler
dieser einen Datei. Auf alle 36 Stilvorlagen angewandt fand sie
GENAU EINE weitere Stelle.

DER FUND: `.wahl2__eintrag` in gate.css

    Zeile 1584  display: block   (mit text-overflow: ellipsis)
    Zeile 2634  display: flex    (fuer Personenbilder, 08.09.2026)

Kein Streit zweier Bauteile, sondern eine spaetere Erweiterung --
und trotzdem ein echter Fehler: `text-overflow: ellipsis` wirkt bei
`display: flex` NICHT auf ein anonymes Flex-Kind. Der Knopf
„… eintragen" in jeder Auswahl des Hauses hat langen Text seither
HART abgeschnitten statt mit „…" gekuerzt.

GEMESSEN -- und nur im Bild zu sehen: `scrollWidth` ist in beiden
Faellen gleich (364 px bei 200 px Breite). Die Zahlen sagten
„kein Unterschied", das Bildschirmfoto zeigte einen. Ein Beleg mehr
dafuer, dass zwei gleiche Zahlen nicht dasselbe Ergebnis bedeuten.

REPARIERT
  - Der eigene Eintrag bekommt einen `.wahl2__name`-Span, wie jeder
    andere Eintrag auch. Damit kuerzt er wieder mit „…".
  - Die zwei Grundregeln sind eine. Wer die erste las, glaubte an
    `display: block` -- 1050 Zeilen weiter stand das Gegenteil.

DIE WACHE
  923 Grundregeln in 36 Stilvorlagen, keine Klasse zweimal
  verschieden gebaut. Gezaehlt wird nur, was sich widersprechen
  kann: nicht dieselbe Klasse in einem Zusammenhang (`.saal .pult`),
  nicht eine Variante in einer Medienabfrage, nicht
  „erst versteckt, dann gezeigt". Sechs Gegenproben, darunter der
  echte Fall vom 28.09. und eine Klasse im Kommentar.

GEPRUEFT: css-klassen, freie-namen, suchfeld, tippziele alle EXIT 0.
This commit is contained in:
2026-09-29 13:45:02 +02:00
parent 0c696f286d
commit 7351820c55
48 changed files with 769 additions and 670 deletions
+85
View File
@@ -899,5 +899,90 @@ function formfehler(text) {
: `Gegenprobe: alle ${proben.length} Faelle richtig eingeordnet`);
}
/* ==== ZWEI GRUNDREGELN, EIN NAME, ZWEI BAUARTEN =====================
Gebaut am 28.09.2026 fuer `reaktion.css`, wo `.stufen` zweimal
stand: einmal fuer die Stufenleiter der Spenden (`display: grid`),
600 Zeilen spaeter fuer die Chat-Leiste (`display: flex`). Die
spaetere gewinnt -- und die Stufenleiter stellte ihre drei Stufen
nebeneinander, 572 Pixel in einer 327 Pixel schmalen Spalte, mit
waagerechter Rollleiste. Auf dem Bildschirmfoto sah das aus wie
ein Platzproblem.
AB JETZT FUER ALLE STILVORLAGEN. Eine Wache, die nur eine Datei
ansieht, findet genau die Fehler dieser einen Datei -- und der
zweite Fund lag in gate.css.
GEZAEHLT WIRD NUR, WAS SICH WIDERSPRECHEN KANN:
- Die Auswahl muss GENAU die Klasse sein. `.saal .pult { … }`
ist dieselbe Klasse in einem Zusammenhang, kein zweites
Bauteil.
- Regeln in einer Medienabfrage zaehlen nicht -- sie sind per
Definition Varianten fuer eine andere Umgebung.
- `display: none` zaehlt nicht. „Erst versteckt, dann gezeigt"
ist das haeufigste Paar im Haus; wer es meldet, baut eine
Warnung, die immer kommt. */
{
const ordner = join(HIER, "..", "workspace", "assets", "css");
const ohneMedien = (t) => t.replace(
/@media[^{]*\{(?:[^{}]|\{[^{}]*\})*\}/g,
(m) => m.replace(new RegExp("[^" + String.fromCharCode(10) + "]", "g"), " "));
const positiv = (w) => w && w !== "none" && !/^none\b/.test(w);
/* LOOKBEHIND STATT VERBRAUCHEN: Mit `(^|[};])` frisst der erste
Treffer die Klammer, die der naechste als Anfang braeuchte --
`.a { … } .a { … }` liefert dann nur EINEN Treffer, und genau
der Fall, um den es geht, faellt durch. */
const grundregeln = (roh) => {
const raus = {};
const text = ohneMedien(roh.replace(/\/\*[\s\S]*?\*\//g,
(m) => m.replace(new RegExp("[^" + String.fromCharCode(10) + "]", "g"), " ")));
for (const m of text.matchAll(
/(?:^|(?<=[};]))\s*(\.(-?[a-zA-Z_][a-zA-Z0-9_-]*))\s*\{([^{}]*)\}/g)) {
const w = (m[3].match(/(?:^|[\s;])display\s*:\s*([^;]+)/) || [, ""])[1].trim();
if (!positiv(w)) continue;
(raus[m[2]] ||= []).push(w);
}
return raus;
};
const streitIn = (roh) => Object.entries(grundregeln(roh))
.filter(([, was]) => new Set(was).size > 1)
.map(([name, was]) => `${name} (${[...new Set(was)].join(" vs. ")})`);
const dateien = readdirSync(ordner).filter((n) => n.endsWith(".css"));
const funde = [];
let geprueft = 0;
for (const name of dateien) {
const roh = readFileSync(join(ordner, name), "utf8");
geprueft += Object.keys(grundregeln(roh)).length;
for (const z of streitIn(roh)) funde.push(`${name}: ${z}`);
}
ok(geprueft > 100 && funde.length === 0,
funde.length ? `Namensstreit: ${funde.join(" | ")}`
: `${geprueft} Grundregeln in ${dateien.length} Stilvorlagen -- `
+ "keine Klasse zweimal verschieden gebaut");
/* GEGENPROBEN IN BEIDE RICHTUNGEN. */
const faelle = [
["der Fall vom 28.09. (zwei Grundregeln, anderes display)",
".stufen { display: grid; } .stufen { display: flex; }", true],
["verstecken und zeigen",
".probe { display: none; } .probe { display: grid; }", false],
["dieselbe Klasse in einem Zusammenhang",
".pult { display: grid; } .saal .pult { display: flex; }", false],
["eine Variante in einer Medienabfrage",
".paar { display: grid; } @media (pointer: coarse) { .paar { display: flex; } }", false],
["zweimal dasselbe display",
".gleich { display: grid; } .gleich { display: grid; }", false],
["eine Klasse im Kommentar",
"/* .a { display: flex; } */ .a { display: grid; }", false],
];
const daneben = faelle
.filter(([, text, soll]) => (streitIn(text).length > 0) !== soll)
.map(([name]) => name);
ok(daneben.length === 0,
daneben.length ? `Gegenprobe daneben bei: ${daneben.join(", ")}`
: `Gegenprobe: alle ${faelle.length} Faelle richtig eingeordnet`);
}
console.log(`\n${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`);
process.exit(fehler ? 1 : 0);