Die Zeitraeume bekommen eine Tafel -- und die Zahl, die fehlte

Filipe: "das muss viel geiler aussehen bitte. ich will dass es auch
seeeehr gut erkennbar ist, genau wie der text drueber. mach das bitte
alles so dass es mega speziell, hochwertig und lesbar ist."

DAS EIGENTLICHE PROBLEM WAR LESBARKEIT, nicht Geschmack. Im
Bildschirmfoto gemessen: Ueberschrift und Erklaersatz standen direkt
auf der Buehne -- einem Foto mit hellrotem Berg. Und die Karte griff
nach `--flaeche-tief`, einer Variablen, die es im ganzen Haus nicht
gibt; gegriffen hat der Ersatzwert mit 72 % Deckung, also kam der Berg
mit. Eine Variable, die nirgends steht, faellt nicht auf.

Jetzt sitzt alles in EINEM Koerper: vierfarbige Fassung, deckendes
Innenglas mit Messraster, die Zeitraeume als eingelassene Felder mit
dunklen Fugen. Das ist nicht neu erfunden, sondern die Rollenkachel der
Anmeldeseite -- ein Haus, eine Handschrift. Typenschild und Hauptzahl
in gebuerstetem Metall, mit vollwertigem Rueckfall.

"GUELTIGE LIVE-GEHEN-TAGE" WURDE BIS HEUTE WEGGEWORFEN. Datenbank,
Anzeigefeld und Importzeile waren da -- nur `spaltenRaten` hatte kein
Muster dafuer. Am 14.09. wurde das alte `/tag/` reparirt, das die
Spalte faelschlich zur Datumsspalte machte; die Reparatur hat den
falschen Empfaenger entfernt und keinen richtigen bestellt. Die
Pruefdatei SCHICKTE den Wert seit dem 14.09. und hat nie nachgesehen,
ob er ankommt. Jetzt steht er als Streifen da, in genau so viele
Kaestchen geteilt, wie der Zeitraum Tage hat.

ZWEI KLASSEN IM WAEHLER, mit Grund: `.spannen__schild` allein (0,1,0)
kam gegen `.inhalt .feldschild` aus start.css (0,2,0) nicht an -- im
Browser gemessen war weder `display: flex` noch die Farbe da.

UND EINE ZEITBOMBE ENTSCHAERFT: pruef-backstage-import rechnete
"morgen" mit `toISOString()` (UTC), der Server mit Ortszeit. Um 00:40
Berlin war das hier berechnete "morgen" in Wahrheit HEUTE -- die
Gegenprobe "ein Datum in der Zukunft wird abgelehnt" fiel taeglich
zwischen Mitternacht und zwei Uhr um. Alle drei Tage werden jetzt aus
einer Stelle abgeleitet.

Gemessen statt behauptet: 43 258 Bildpunkte hinter Ueberschrift und
Satz abgetastet, kein einziger rot. Gegenprobe mit weggenommenem
Innenglas: 2 857 rote -- die Messung kann Rot also sehen.

pruef-backstage-import 133 (war 127), pruef-xlsx 75 (war 67),
pruef-css-klassen, pruef-leistung-optik 59, pruef-leistung: alle gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-16 00:59:51 +02:00
co-authored by Claude Opus 5
parent 04f87603f6
commit 984f0dd7a9
35 changed files with 929 additions and 424 deletions
+149 -8
View File
@@ -40,9 +40,26 @@ const BASIS = `http://127.0.0.1:${PORT}`;
const ordner = mkdtempSync(join(tmpdir(), "ws-backstage-"));
const DB = join(ordner, "workspace.db");
const jetzt = new Date().toISOString();
const heute = new Date().toISOString().slice(0, 10);
const gestern = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
const morgen = new Date(Date.now() + 86400000).toISOString().slice(0, 10);
/* HEUTE IST DER TAG, DEN DER SERVER MEINT -- nicht der in Greenwich.
16.09.2026, 00:40 Berlin: `toISOString()` sagte "2026-09-15", der
Server (heuteLokal(), Ortszeit) sagte "2026-09-16". Damit war das
hier berechnete "morgen" in Wahrheit HEUTE, und die Gegenprobe
"ein Datum in der Zukunft wird abgelehnt" schlug fehl -- an der
Zeitzone, nicht am Code. Zwischen Mitternacht und zwei Uhr morgens
lag diese Pruefung also taeglich zwei Stunden lang falsch.
Eine Zeitbombe genau der Sorte, die schon einmal notiert wurde
(Shop, 06.09.2026): Die Wanduhr darf Eingabe sein, nie Annahme.
ALLE DREI WERDEN AUS EINER STELLE ABGELEITET -- drei eigene
Rechnungen waeren drei Gelegenheiten, wieder auseinanderzulaufen. */
const tagLokal = (versatz) => {
const d = new Date(Date.now() + versatz * 86400000);
const p2 = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
};
const heute = tagLokal(0);
const gestern = tagLokal(-1);
const morgen = tagLokal(1);
let gemacht = 0, fehler = 0;
const ok = (b, text) => {
@@ -782,6 +799,135 @@ console.log("\n=== Die Seite ===");
`und die Spanne groesser als ein Nebenwert (${groessen.spanne} px)`);
ok(/\d/.test(groessen.text), `die Hauptzahl traegt Diamanten ("${groessen.text}")`);
/* ---- "11 VON 13 TAGEN LIVE" (16.09.2026) ------------------------
Die Spalte "Gültige LIVE-Gehen-Tage" steht seit dem 14.09. in
dieser Pruefdatei -- und wurde die ganze Zeit weggeworfen, weil
`spaltenRaten` kein Muster dafuer hatte. Die Pruefung hat den Wert
zwar GESCHICKT, aber nie nachgesehen, ob er ankommt.
GEMESSEN WIRD DER ANTEIL IN PIXELN, nicht das Vorhandensein des
Streifens: Ein Streifen, der immer voll ist, zeigt keine Zahl,
sondern eine Dekoration. 11 von 13 sind 85 %. */
const lauf = await seite.evaluate(() => {
const bahn = document.querySelector(".spanne__lauf-bahn");
const teil = document.querySelector(".spanne__lauf-teil");
if (!bahn || !teil) return null;
return {
anteil: Math.round((teil.getBoundingClientRect().width
/ bahn.getBoundingClientRect().width) * 100),
text: document.querySelector(".spanne__lauf-text")?.textContent || "",
};
});
ok(!!lauf, "der Streifen der gueltigen Tage steht auf der Karte");
ok(lauf && lauf.text.includes("11 von 13 Tagen live"),
`er nennt die Zahl im Klartext ("${lauf?.text}")`);
ok(lauf && Math.abs(lauf.anteil - 85) <= 2,
`und ist zu ${lauf?.anteil} % gefuellt (11 von 13 = 85 %)`);
/* ---- DER EIGENTLICHE WUNSCH: LESBARKEIT (16.09.2026) ------------
Filipe: "ich will dass es auch seeeehr gut erkennbar ist, genau
wie der text drüber."
Hinter dem Inhalt dieser Seite liegt die Buehne -- ein Foto mit
einem HELLROTEN Berg. Ueberschrift und Erklaersatz standen bis
heute direkt darauf.
GEMESSEN WIRD AN ECHTEN BILDPUNKTEN, nicht an der Farbangabe im
Stil: Die weiss nichts davon, was hinter ihr liegt. Das Foto ist
stark rot, das Innenglas blaeulich-dunkel (#0b111c: R=11, B=28) --
ein Bildpunkt mit R deutlich ueber B kann dort also nur vom Foto
stammen.
WAS BEIM ERSTEN ANLAUF SCHIEFGING, und es ist lehrreich: Die
Messung meldete zehn rote Bildpunkte -- und hatte recht. Sie kamen
aber nicht vom Foto, sondern vom ANSCHLAG des Typenschilds, den
drei roten Kerben, die ich selbst dorthin gesetzt habe. Eine
Messung, die "alles Rote ist das Foto" annimmt, misst irgendwann
die eigene Gestaltung. Der Anschlag wird deshalb ausgespart -- und
zwar mit seiner EIGENEN Breite aus dem Stil, nicht mit einer
abgeschriebenen Zahl. */
const roteBildpunkte = async (b64) => {
const bild = new Image();
bild.src = "data:image/png;base64," + b64;
await bild.decode();
const c = document.createElement("canvas");
c.width = bild.width; c.height = bild.height;
const g = c.getContext("2d");
g.drawImage(bild, 0, 0);
const skala = bild.width / window.innerWidth;
const schild = document.querySelector(".spannen__schild");
const satz = document.querySelector(".spannen__satz");
if (!schild || !satz) return null;
/* Der Anschlag ist ein ::before und hat kein eigenes Rechteck.
Seine Breite und der Abstand dahinter stehen aber im Stil --
daraus wird die Stelle abgeleitet, ab der die Zeile beginnt. */
const vor = getComputedStyle(schild, "::before");
const st = getComputedStyle(schild);
const versatz = (parseFloat(vor.width) || 0) + (parseFloat(st.columnGap) || 0) + 2;
const sr = schild.getBoundingClientRect();
const zr = satz.getBoundingClientRect();
const felder = [
{ name: "Ueberschrift", l: sr.left + versatz, t: sr.top, w: sr.width - versatz, h: sr.height },
{ name: "Erklaersatz", l: zr.left, t: zr.top, w: zr.width, h: zr.height },
];
let punkte = 0, rot = 0;
for (const f of felder) {
const w = Math.max(1, Math.round(f.w * skala));
const h = Math.max(1, Math.round(f.h * skala));
const p = g.getImageData(Math.round(f.l * skala), Math.round(f.t * skala), w, h).data;
for (let i = 0; i < p.length; i += 4) {
punkte++;
if (p[i] > p[i + 2] + 20) rot++;
}
}
return { punkte, rot };
};
const durchsicht = await seite.evaluate(roteBildpunkte,
(await seite.screenshot({ type: "png" })).toString("base64"));
ok(durchsicht && durchsicht.punkte > 5000,
`hinter Ueberschrift und Erklaersatz wurden ${durchsicht?.punkte} Bildpunkte abgetastet`);
ok(durchsicht && durchsicht.rot === 0,
`kein einziger davon traegt das Rot des Fotos (${durchsicht?.rot})`);
/* Das Bild der fertigen Tafel -- MIT Glas, also so, wie Filipe sie
sieht. Es entsteht hier und nicht am Ende: Am Ende ist ein anderer
Creator geoeffnet, dessen Zeitraum keine gueltigen Tage hat. */
if (process.env.SCHIRM) {
await seite.locator("#spannen-block").screenshot(
{ path: join(HIER, "pruef-spannen.png") });
}
/* GEGENPROBE -- und sie ist hier das Wichtigste.
"Null rote Bildpunkte" koennte auch heissen, dass gar kein Foto
dahinterliegt, dass das Fenster zu klein ist oder dass ich die
falsche Stelle abtaste. Also wird das Innenglas fuer einen
Augenblick weggenommen und werden DIESELBEN Stellen mit
DEMSELBEN Abtaster noch einmal gemessen: Kommt das Rot dann durch,
war es vorher das Glas, das es aufgehalten hat -- und die Messung
kann ueberhaupt Rot sehen.
Genau die Sorte Pruefung, die ohne Gegenprobe jahrelang gruen
bleibt und nichts beweist. */
await seite.addStyleTag({ content:
".spannen-tafel { background: none !important; }"
+ ".spannen-tafel::before { display: none !important; }"
+ ".spannen, .spanne { background: none !important; }" });
await seite.waitForTimeout(250);
const ohneGlas = await seite.evaluate(roteBildpunkte,
(await seite.screenshot({ type: "png" })).toString("base64"));
ok(ohneGlas && ohneGlas.rot > 200,
`ohne das Innenglas kommt das Foto sofort durch (${ohneGlas?.rot} rote Bildpunkte) `
+ "-- die Messung kann Rot also sehen");
await seite.reload({ waitUntil: "networkidle" });
await seite.waitForTimeout(1500);
/* ---- EINE WOCHE OHNE EINEN EINZIGEN TAG (14.09.2026) ------------
Filipe: "wieso werden die zahlen oben nicht ausgefuellt von der
excel datei?"
@@ -851,11 +997,6 @@ console.log("\n=== Die Seite ===");
"mit einem einzigen Tag sind die Karten wieder da");
if (process.env.SCHIRM) {
await seite.locator("#spannen-block").screenshot(
{ path: join(HIER, "pruef-spannen.png") });
}
await seite.click("#netz-zu").catch(() => {});
await seite.waitForTimeout(300);
/* Gemessen wird SATZ FUER SATZ, nicht der Block. Ein Block, der
+24
View File
@@ -201,6 +201,30 @@ console.log("\n=== Vom Excel bis zur Spaltenzuordnung ===");
`"${name}" ${sollTreffen ? "ist" : "ist NICHT"} die Datumsspalte (erkannt: ${trifft})`);
}
/* ---- UND WO LANDET SIE DANN? (16.09.2026) ----------------------
Die Gegenprobe oben sagte nur, dass "Gültige LIVE-Gehen-Tage"
KEINE Datumsspalte ist. Das war richtig -- und unvollständig: Die
Spalte hatte danach ueberhaupt keinen Empfaenger mehr und lief ins
Leere. Datenbankspalte, Anzeigefeld und Importzeile waren da, nur
das Muster fehlte.
DAS IST DIE GENAUE SORTE FEHLER, DIE EINE GEGENPROBE ALLEIN NICHT
FINDET: "trifft das Falsche nicht" ist kein Beweis dafuer, dass es
das Richtige trifft. Deshalb steht beides jetzt nebeneinander. */
ok(tabelle[0][zu.gueltige_tage] === "Gültige LIVE-Gehen-Tage",
`"Gültige LIVE-Gehen-Tage" landet als gueltige_tage (Spalte ${zu.gueltige_tage})`);
ok(zahlLesen(tabelle[1][zu.gueltige_tage]) === 11,
`und traegt die Zahl ${zahlLesen(tabelle[1][zu.gueltige_tage])}`);
for (const [name, soll] of [
["Gültige LIVE-Gehen-Tage", true], ["Valid go LIVE days", true],
["Tage seit dem Beitritt", false], ["Livestreams", false],
["Diamanten", false], ["LIVE-Dauer", false],
]) {
const trifft = spaltenRaten([name]).gueltige_tage === 0;
ok(trifft === soll,
`"${name}" ${soll ? "ist" : "ist NICHT"} die Spalte der gueltigen Tage (erkannt: ${trifft})`);
}
/* Und die Werte, die dahinter herauskommen. */
ok(zahlLesen(tabelle[1][zu.diamanten]) === 12480,
`"12.480" wird zu ${zahlLesen(tabelle[1][zu.diamanten])} und nicht zu 12,48`);
+21
View File
@@ -762,6 +762,27 @@ export function spaltenRaten(kopf) {
"Daten" die englische Silbe "date" steckt. Auf Zufall soll das
nicht beruhen. */
tag: /datum|date|zeitraum|^\s*tage?\s*$|^\s*day\s*$/i,
/* "Gültige LIVE-Gehen-Tage" (16.09.2026).
DIESE SPALTE STEHT IN JEDER BACKSTAGE-AUSGABE UND WURDE BIS
HEUTE WEGGEWORFEN. Die Datenbank hat die Spalte, die Anzeige hat
das Feld, der Import hat die Zeile -- nur hier fehlte das
Muster, und damit blieb `gueltige_tage` auf dem ganzen Weg leer.
Aufgefallen ist es nicht beim Lesen des Codes, sondern beim
Nachsehen, warum "Gültige Tage" auf der Karte nie erschien.
WARUM ES VORHER NICHT AUFFIEL: Bis zum 14.09. hat das Muster
`tag: /.../` mit einem blossen `/tag/` genau diese Spalte
gefressen und als DATUMSSPALTE verbucht (Wert: "11"). Als das
repariert wurde, fiel die Spalte durchs Raster -- die Reparatur
hat den falschen Empfänger entfernt, aber keinen richtigen
bestellt.
Es trifft die deutsche und die englische Schreibweise. Kein
anderes Muster oben oder unten greift daneben: "dauer_min"
verlangt `live` ZUSAMMEN mit `zeit`, und `tag` nur ganze
Wörter. */
gueltige_tage: /g(?:ü|ue)ltig.*tag|valid.*day|live.*geh|go.*live.*day/i,
diamanten: /diamant|diamond/i,
dauer_min: /dauer|duration|minut|zeit.*live|live.*zeit/i,
zuschauer_avg: /(durchschnitt|avg|average|⌀|ø).*(zuschauer|viewer)|(zuschauer|viewer).*(durchschnitt|avg)/i,