Filipe: "keine kryptischen oder technischen fehlermeldungen, sondern
klare aussagen darueber, was passiert ist und wie man das problem
loesen kann."
DAS PROBLEM, NACHGEMESSEN.
Der Server antwortet im Fehlerfall mit { fehler: "..." }. Darin stehen
ZWEI verschiedene Dinge, und von aussen sehen sie gleich aus:
Maschinenkennungen (nicht_verfuegbar, nicht_gefunden, ungueltig -- 43
verschiedene) und fertige deutsche Saetze.
An 84 Stellen stand `textContent = d.fehler` -- ungefiltert. Wer beim
Hochladen einer Datei Pech hatte, las woertlich "nicht_verfuegbar" auf
dem Bildschirm und wusste nicht einmal, ob er selbst schuld war.
Gezaehlt: 474 Antworten mit "nicht_verfuegbar", 392 mit
"nicht_gefunden", 125 mit "ungueltig".
DREI AUSGAENGE, NICHT ZWEI.
`sagWas()` in workspace/assets/js/meldung.js:
bekannte Kennung -> ihr Satz
unbekannte Kennung -> der Ersatzsatz der Stelle, NIE die Kennung
(sie geht in die Konsole, wo sie jemandem
auffaellt, der sie beheben kann)
fertiger Satz -> unveraendert durch
Erkannt wird eine Kennung daran, dass sie nur aus Kleinbuchstaben,
Ziffern und Unterstrichen besteht. Ein deutscher Satz hat immer
Leerzeichen; eine Kennung nie. Die Unterscheidung ist entschieden,
nicht geraten.
UND WARUM DIE TABELLE NICHT ALTERN KANN.
Eine von Hand gepflegte Liste ist hier schon zweimal teuer geworden
(die abgeschriebene Spaltenliste, die feste Umbruchschwelle). Deshalb
rechnet pruef-meldungen.mjs die Kennungen AUS DEM SERVER aus statt sie
zu kennen -- eine neue ohne Satz macht sie rot. Man kann es nicht mehr
vergessen.
Sie prueft drei Dinge und beweist zu jedem, dass sie auch "nicht in
Ordnung" sagen kann:
1. vollstaendig -- jede Kennung hat einen Satz
2. angeschlossen -- keine Rohanzeige, und jede Seite, deren Skript
sagWas benutzt, laedt auch meldung.js
3. lesbar -- kein Satz enthaelt Kennung, Zahl oder englisches Wort
GEFUNDEN HAT SIE SOFORT EINEN ECHTEN FEHLER: leistung.html laedt seine
Skripte ohne `defer` und fiel damit durch meinen Einbau. Dort waere
sagWas nicht definiert gewesen -- aus einer Fehlermeldung waere ein
Absturz geworden, also schlimmer als vorher.
NEBENBEFUND, DER MICH FAST EINE FALSCHE MELDUNG GEKOSTET HAETTE:
"alter_offen" heisst NICHT "etwas Aelteres ist offen", sondern
"Altersbestaetigung steht noch aus" (workspace.js:4864). Geraten
haette ich es falsch uebersetzt.
GEMESSEN: pruef-meldungen 8/0, alle 46 Skripte syntaktisch in Ordnung.
Co-Authored-By: Claude Opus 5 <[email protected]>
220 lines
10 KiB
JavaScript
220 lines
10 KiB
JavaScript
/* =====================================================================
|
|
pruef-meldungen.mjs — Kein Benutzer liest je eine Maschinenkennung.
|
|
|
|
Filipe, 19.09.2026: „keine kryptischen oder technischen
|
|
Fehlermeldungen, sondern klare Aussagen darüber, was passiert ist
|
|
und wie man das Problem lösen kann."
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM DIESE PRÜFUNG DIE EIGENTLICHE ARBEIT IST
|
|
|
|
Die Übersetzungstabelle in `workspace/assets/js/meldung.js` zu
|
|
schreiben war eine Stunde Arbeit. Sie in einem halben Jahr noch
|
|
vollständig zu haben, ist die schwierigere Aufgabe — und zwar aus
|
|
einem Grund, den dieses Haus teuer gelernt hat:
|
|
|
|
„Eine abgeschriebene Liste ist dieselbe Falle wie eine feste Zahl."
|
|
(11.09.2026, als die Umstellung drei Spalten verlor, obwohl ein
|
|
Kommentar direkt daneben davor warnte.)
|
|
|
|
Wer morgen `fehler: "zu_frueh"` erfindet, wird nicht daran denken,
|
|
hier einen Satz nachzutragen. Er merkt es auch nicht: Die Oberfläche
|
|
zeigt dann klaglos den Ersatzsatz. Es sieht aus, als funktioniere es.
|
|
|
|
Deshalb rechnet diese Prüfung die Liste der Kennungen AUS DEM SERVER
|
|
aus, statt sie zu kennen. Eine neue Kennung ohne Satz macht sie rot.
|
|
Man KANN es nicht mehr vergessen.
|
|
|
|
---------------------------------------------------------------------
|
|
DREI DINGE WERDEN GEPRÜFT
|
|
|
|
1. VOLLSTÄNDIG — jede Kennung, die der Server schicken kann, hat
|
|
einen Satz.
|
|
2. ANGESCHLOSSEN — keine Seite zeigt `.fehler` noch roh an, und
|
|
jede Seite, deren Skript `sagWas` benutzt, lädt auch
|
|
`meldung.js`. (Der zweite Teil ist der wichtigere: Ohne die
|
|
Datei wäre `sagWas` nicht definiert, und aus einer Fehlermeldung
|
|
würde ein Absturz — schlimmer als vorher.)
|
|
3. LESBAR — kein Satz enthält eine Kennung, eine nackte Zahl oder
|
|
ein englisches Wort.
|
|
|
|
Dazu drei GEGENPROBEN. Eine Prüfung, die immer bestätigt, bestätigt
|
|
nichts (01.09.2026): Jede der drei muss beweisen, dass sie auch
|
|
„nicht in Ordnung" sagen kann.
|
|
|
|
AUFRUF: node server/pruef-meldungen.mjs
|
|
Läuft in unter einer Sekunde, ohne Server, ohne Browser.
|
|
===================================================================== */
|
|
|
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const WURZEL = join(HIER, "..");
|
|
const JS = join(WURZEL, "workspace", "assets", "js");
|
|
const SEITEN = join(WURZEL, "workspace");
|
|
|
|
let ok = 0, fehl = 0;
|
|
const pruef = (name, bedingung, zusatz = "") => {
|
|
if (bedingung) { ok++; console.log(` OK ${name}`); }
|
|
else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); }
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die Tabelle aus meldung.js lesen — ohne einen Browser zu bauen.
|
|
|
|
`meldung.js` hängt sich an `window`. Statt eine halbe Browserwelt
|
|
nachzustellen, wird der Block zwischen `var SAETZE = {` und der
|
|
schließenden Klammer ausgeschnitten und als JavaScript ausgewertet.
|
|
Das ist ehrlicher als ein zweiter Auszug der Namen: Es ist dieselbe
|
|
Quelle, kein abgeschriebener Zwilling.
|
|
------------------------------------------------------------------- */
|
|
function saetzeLesen() {
|
|
const quelle = readFileSync(join(JS, "meldung.js"), "utf8");
|
|
const start = quelle.indexOf("var SAETZE = {");
|
|
if (start < 0) return null;
|
|
const auf = quelle.indexOf("{", start);
|
|
let tiefe = 0, ende = -1;
|
|
for (let i = auf; i < quelle.length; i++) {
|
|
if (quelle[i] === "{") tiefe++;
|
|
else if (quelle[i] === "}") { tiefe--; if (tiefe === 0) { ende = i; break; } }
|
|
}
|
|
if (ende < 0) return null;
|
|
// eslint-disable-next-line no-new-func
|
|
return Function(`"use strict"; return (${quelle.slice(auf, ende + 1)});`)();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Alle Kennungen, die der Server schicken kann.
|
|
|
|
ABGELEITET, NICHT ABGESCHRIEBEN. Gesucht wird `fehler: "…"` in allen
|
|
Server-Dateien; als Kennung gilt, was nur aus Kleinbuchstaben,
|
|
Ziffern und Unterstrichen besteht. Alles andere ist ein fertiger
|
|
Satz und braucht keine Übersetzung.
|
|
------------------------------------------------------------------- */
|
|
function kennungenAusServer() {
|
|
const gefunden = new Map();
|
|
for (const datei of readdirSync(HIER)) {
|
|
if (!datei.endsWith(".js")) continue;
|
|
if (datei.startsWith("pruef-") || datei.startsWith("test-")) continue;
|
|
const text = readFileSync(join(HIER, datei), "utf8");
|
|
for (const m of text.matchAll(/fehler:\s*"([a-z0-9_]+)"/g)) {
|
|
if (!gefunden.has(m[1])) gefunden.set(m[1], datei);
|
|
}
|
|
}
|
|
return gefunden;
|
|
}
|
|
|
|
console.log("\n=== Meldungen: sagt das Haus ganze Sätze? ===\n");
|
|
|
|
const SAETZE = saetzeLesen();
|
|
/* DER DRITTE AUSGANG. Lässt sich die Tabelle nicht lesen, ist das
|
|
weder „in Ordnung" noch „ein Satz fehlt" — es heißt, die Prüfung
|
|
kann ihre Frage nicht beantworten. Dann bricht sie ab, statt grün
|
|
zu melden. Genau dieser Fall hat am 03.09. wochenlang eine
|
|
Pflichtprüfung wertlos gemacht. */
|
|
if (!SAETZE) {
|
|
console.log(" KONNTE NICHT NACHSEHEN — in meldung.js ist kein Block `var SAETZE = {` zu finden.");
|
|
console.log(" Grund: entweder wurde die Datei umgebaut oder sie fehlt.");
|
|
console.log(" Ohne sie lässt sich nicht sagen, ob Sätze fehlen. Abbruch.\n");
|
|
process.exit(2);
|
|
}
|
|
|
|
const kennungen = kennungenAusServer();
|
|
console.log(` (${kennungen.size} Kennungen im Server, ${Object.keys(SAETZE).length} Sätze in der Tabelle)\n`);
|
|
|
|
/* ---- 1. Vollständig ------------------------------------------------- */
|
|
const ohneSatz = [...kennungen.keys()].filter((k) => !SAETZE[k]);
|
|
pruef("Jede Server-Kennung hat einen Satz",
|
|
kennungen.size > 0 && ohneSatz.length === 0,
|
|
ohneSatz.length ? `ohne Satz: ${ohneSatz.map((k) => `${k} (${kennungen.get(k)})`).join(", ")}` : "");
|
|
|
|
/* Die Zahl gehört in die BEDINGUNG, nicht nur in den Meldetext.
|
|
`every()` auf einem leeren Feld liefert `true` — eine Prüfung, die
|
|
nichts gefunden hat, wäre sonst grün (01.09.2026, Shop). */
|
|
pruef("Es wurden überhaupt Kennungen gefunden", kennungen.size >= 30,
|
|
`nur ${kennungen.size} — findet der Suchausdruck noch, was er soll?`);
|
|
|
|
/* ---- 2. Angeschlossen ----------------------------------------------- */
|
|
const skripte = readdirSync(JS).filter((f) => f.endsWith(".js"));
|
|
|
|
/* 2a: Keine Rohanzeige mehr. Gesucht wird `.fehler` in derselben Zeile
|
|
wie eine Ausgabe — aber nur dort, wo KEIN `sagWas` und kein
|
|
Vergleich (`===`) steht. Ein Vergleich übersetzt selbst. */
|
|
const roh = [];
|
|
for (const f of skripte) {
|
|
if (f === "meldung.js") continue;
|
|
const zeilen = readFileSync(join(JS, f), "utf8").split("\n");
|
|
zeilen.forEach((z, i) => {
|
|
if (!/\.fehler/.test(z)) return;
|
|
if (!/(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)) return;
|
|
if (/sagWas/.test(z) || /\.fehler\s*===/.test(z) || /\[\s*\w+\.fehler\s*\]/.test(z)) return;
|
|
roh.push(`${f}:${i + 1}`);
|
|
});
|
|
}
|
|
pruef("Keine Seite zeigt eine Kennung roh an", roh.length === 0,
|
|
roh.length ? roh.join(", ") : "");
|
|
|
|
/* 2b: Wer sagWas benutzt, muss meldung.js laden. DAS IST DIE
|
|
WICHTIGERE HÄLFTE: Fehlt die Datei, ist `sagWas` nicht definiert,
|
|
und aus einer Fehlermeldung wird ein Absturz. */
|
|
const benutzt = new Set(skripte.filter(
|
|
(f) => f !== "meldung.js" && /\bsagWas\(/.test(readFileSync(join(JS, f), "utf8"))));
|
|
const luecken = [];
|
|
for (const seite of readdirSync(SEITEN).filter((f) => f.endsWith(".html"))) {
|
|
const html = readFileSync(join(SEITEN, seite), "utf8");
|
|
const geladen = [...html.matchAll(/src="assets\/js\/([a-z0-9-]+)\.js/g)].map((m) => m[1] + ".js");
|
|
const braucht = geladen.filter((g) => benutzt.has(g));
|
|
if (braucht.length && !geladen.includes("meldung.js")) {
|
|
luecken.push(`${seite} (braucht es wegen ${braucht.join(", ")})`);
|
|
}
|
|
}
|
|
pruef("Jede Seite, die sagWas braucht, lädt meldung.js",
|
|
benutzt.size > 0 && luecken.length === 0,
|
|
luecken.length ? luecken.join("; ") : (benutzt.size ? "" : "keine Datei benutzt sagWas — stimmt das?"));
|
|
|
|
/* ---- 3. Lesbar ------------------------------------------------------ */
|
|
const schlecht = [];
|
|
for (const [k, satz] of Object.entries(SAETZE)) {
|
|
if (/[a-z]+_[a-z]+/.test(satz)) schlecht.push(`${k}: enthält eine Kennung`);
|
|
else if (/\b(?:400|401|403|404|409|429|500|503)\b/.test(satz)) schlecht.push(`${k}: enthält eine Fehlernummer`);
|
|
else if (/\b(error|failed|invalid|denied|request|server error)\b/i.test(satz)) schlecht.push(`${k}: englisches Wort`);
|
|
else if (satz.length < 15) schlecht.push(`${k}: zu kurz ("${satz}")`);
|
|
}
|
|
pruef("Jeder Satz ist deutsch, ohne Kennung und ohne Zahl",
|
|
Object.keys(SAETZE).length > 0 && schlecht.length === 0,
|
|
schlecht.length ? schlecht.join("; ") : "");
|
|
|
|
/* =====================================================================
|
|
DIE GEGENPROBEN
|
|
|
|
Zu jeder Prüfung, die „alles in Ordnung" sagen kann, gehört der
|
|
Beweis, dass sie auch „nicht in Ordnung" sagen kann. Sonst weiß
|
|
niemand, ob sie überhaupt hinsieht.
|
|
===================================================================== */
|
|
console.log("\n --- Gegenproben (jede MUSS anschlagen) ---");
|
|
|
|
const g1 = (() => { const t = { ...SAETZE }; delete t.nicht_verfuegbar;
|
|
return [...kennungen.keys()].filter((k) => !t[k]).length > 0; })();
|
|
pruef("Gegenprobe: fehlt ein Satz, schlägt Prüfung 1 an", g1);
|
|
|
|
const g2 = (() => {
|
|
const z = " $('x').textContent = d.fehler || 'Ging nicht.';";
|
|
return /\.fehler/.test(z)
|
|
&& /(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)
|
|
&& !/sagWas/.test(z) && !/\.fehler\s*===/.test(z);
|
|
})();
|
|
pruef("Gegenprobe: eine Rohanzeige würde Prüfung 2a finden", g2);
|
|
|
|
const g3 = (() => {
|
|
const s = "Fehler 503: request denied";
|
|
return /[a-z]+_[a-z]+/.test(s)
|
|
|| /\b(?:400|401|403|404|409|429|500|503)\b/.test(s)
|
|
|| /\b(error|failed|invalid|denied|request)\b/i.test(s);
|
|
})();
|
|
pruef("Gegenprobe: ein unlesbarer Satz würde Prüfung 3 finden", g3);
|
|
|
|
console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`);
|
|
process.exit(fehl ? 1 : 0);
|