Meldungen: eine Tabelle, 27 fehlende Saetze, und die Pruefung sagt jetzt wo

pruef-meldungen war rot, und zwar schon vor der Arbeit dieser Woche
(mit einer zweiten Arbeitskopie auf HEAD belegt). Gemeldet waren "27
Kennungen ohne deutschen Satz" und drei Seiten, die eine Kennung roh
anzeigen. Nachgemessen war die Lage anders -- und das ist der Kern:

  18 Kennungen hatten NIRGENDS einen Satz. Wer sie traf, las den
     Ersatzsatz "Das hat nicht geklappt" und erfuhr damit nicht, dass
     die Karte entfernt wurde oder dass es zu einer Kachel nur eine
     geben kann.

   9 hatten einen Satz -- aber nur in einer privaten Tabelle in
     manager-ziele.js. Ueber ihr stand als Begruendung woertlich: "An
     EINER Stelle: Stuende jeder dort, wo er gebraucht wird, hiesse
     dieselbe Absage an zwei Stellen verschieden." Der Satz war
     richtig; nur war diese Tabelle selbst inzwischen die zweite
     Stelle. Drei Kennungen standen in BEIDEN mit verschiedenem
     Wortlaut.

ZWEI DER DREI ROHANZEIGEN WAREN FEHLALARME: manager-ziele.js:888 und
:1322 riefen satzZu(d.fehler) -- eine echte Uebersetzung, die die
Pruefung nur nicht kannte. Echt war chat.js:3542 mit
`Ging nicht: ${d.fehler}`.

Gemacht:
  - 27 Saetze nach meldung.js (157 -> 184). Der Wortlaut der neun ist
    aus manager-ziele.js uebernommen, nicht neu erfunden.
  - Zwei Haussaetze seitenneutral gemacht: name_fehlt sprach von einer
    "Karte", nicht_deins von einem "Stueck" -- beide Kennungen kommen
    gemessen von Seiten, die weder Karte noch Stueck haben. Ohne das
    waere das Aufloesen der Zweittabelle eine VERSCHLECHTERUNG gewesen.
  - FEHLERTEXT und satzZu in manager-ziele.js weg, 8 Aufrufe auf
    sagWas umgestellt (gleiche Form: sagWas(fehler, ersatz)).
    manager-ziele.html laedt meldung.js jetzt vor der Seite.
  - chat.js zeigt einen Satz statt der Kennung.

An der Pruefung:
  - PRUEFUNG 4 (neu): Nur die Haustabelle uebersetzt Serverkennungen.
    Ausnahmen werden benannt, mit Gegenwache, dass jede noch existiert.
    Der Sucher ist eng gefasst, weil ein weicherer vier Treffer meldete
    und zwei davon Fehlalarme waren (`leer:` in den Spaltenlisten von
    aufgaben.js und chat.js) sowie einer kein Fehler war (reaktion.js
    uebersetzt schreib_grund in einen Platzhaltertext, nicht fehler:).
  - PRUEFUNG 1 nennt jetzt die Zweitstelle. Ohne das schickt der Befund
    in die falsche Richtung -- wer "27 ohne Satz" liest, schreibt 27
    neue und hat danach drei Tabellen.
  - Die Kommentar-Entfernung steht auf Modulebene, mit Selbsttest und
    drittem Ausgang: raeumt sie alles weg, ist jede Suche darueber
    gruen.

10 Pruefungen, 0 Fehler (vorher 8 mit 2 Fehlschlaegen) -- die Zahl ist
gestiegen, nichts ist stillschweigend weggefallen.

SECHS GEGENPROBEN, alle sechs schlagen an. Drei waren im ersten Lauf
falsch gebaut (CRLF-Anker, Variable statt Schluessel umbenannt, eine
Sabotage die nichts tat) -- das ist jeweils als Begruendung vermerkt,
weil eine blinde Sabotage genauso aussieht wie eine blinde Pruefung.
Gegenprobe 1 hat dabei einen ECHTEN Fehler in meinem neuen Pruefcode
gefunden: woSonst() griff auf `skripte` zu, das erst spaeter angelegt
wird. Im gruenen Zustand laeuft diese Zeile nie -- der Absturz waere
genau dann gekommen, wenn ein Satz fehlt, also wenn man die Meldung am
noetigsten braucht.

Geprueft: meldungen 10, manager-ziele 229, struktur 102, chat 80,
chat-optik 84, chat-anhaenge 168, workspace-seiten 37, formulare 23,
entwicklung 79, xlsx 75, lesbarkeit 14, zeichen 8, deutsche-texte 12 --
alle gruen, keine Zahl gesunken.
This commit is contained in:
2026-10-08 21:15:36 +02:00
parent 0784ad61ce
commit 04cfb06d33
52 changed files with 1081 additions and 744 deletions
+216 -6
View File
@@ -55,6 +55,43 @@ const WURZEL = join(HIER, "..");
const JS = join(WURZEL, "workspace", "assets", "js");
const SEITEN = join(WURZEL, "workspace");
/* ---------------------------------------------------------------------
Quelltext ohne Kommentare — an EINER Stelle.
WARUM DAS SEIN MUSS: Eine Suche über eine Datei findet Wörter auch
dort, wo sie nur ERWÄHNT werden. In diesem Haus stehen in vielen
Dateien mehr Kommentarzeilen als Anweisungen — am 24.09.2026
meldete diese Prüfung einen längst behobenen Fehler weiter, weil
der Kommentar daneben die alte Zeile zitierte.
Zeichenketten mit // darin (Adressen) bleiben unberührt:
geschnitten wird nur, wo kein Doppelpunkt und kein
Anführungszeichen davorsteht.
------------------------------------------------------------------- */
const ohneKommentareJs = (roh) => roh
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:"'\\])\/\/[^\n]*/gm, "$1");
/* GEGENPROBE AN DER ENTFERNUNG SELBST. Räumt sie zu viel weg, ist
jede Suche darüber grün — über einem leeren Text findet niemand
etwas. Zwei Proben: ein Kommentar muss verschwinden, Quelltext
und eine Adresse müssen stehen bleiben. */
{
const probe = ohneKommentareJs(
'const a = 1; /* fehler: "weg_damit" */\n'
+ 'const b = "https://x.test/y"; // fehler: "auch_weg"\n'
+ 'res.json({ fehler: "bleibt_da" });');
const gut = !probe.includes("weg_damit") && !probe.includes("auch_weg")
&& probe.includes("bleibt_da") && probe.includes("https://x.test/y");
if (!gut) {
console.log(" KONNTE NICHT NACHSEHEN — die Kommentar-Entfernung"
+ " arbeitet nicht wie erwartet. Ohne sie wäre jede Suche"
+ " darüber wertlos. Abbruch.");
console.log(" Herausgekommen ist:\n" + probe + "\n");
process.exit(2);
}
}
let ok = 0, fehl = 0;
const pruef = (name, bedingung, zusatz = "") => {
if (bedingung) { ok++; console.log(` OK ${name}`); }
@@ -116,10 +153,7 @@ function kennungenAusServer() {
Zeichenketten mit // darin (Adressen) bleiben unberuehrt:
geschnitten wird nur, wo kein Doppelpunkt und kein
Anfuehrungszeichen davorsteht. */
const ohneKommentare = (roh) => roh
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:"'\\])\/\/[^\n]*/gm, "$1");
const text = ohneKommentare(readFileSync(join(HIER, datei), "utf8"));
const text = ohneKommentareJs(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);
}
@@ -145,11 +179,57 @@ if (!SAETZE) {
const kennungen = kennungenAusServer();
console.log(` (${kennungen.size} Kennungen im Server, ${Object.keys(SAETZE).length} Sätze in der Tabelle)\n`);
/* Die Browserdateien. SIE MUSS HIER STEHEN, nicht erst in Abschnitt 2:
Prüfung 1 braucht sie über `woSonst`, und eine `const` weiter unten
ist zu diesem Zeitpunkt noch nicht zugänglich. Gefunden hat das die
Gegenprobe, nicht der grüne Lauf — im grünen Zustand wird `woSonst`
nie gerufen, und der Absturz wäre genau dann gekommen, wenn ein
Satz fehlt, also wenn man die Meldung am nötigsten braucht. */
const skripte = readdirSync(JS).filter((f) => f.endsWith(".js"));
/* ---- 1. Vollständig ------------------------------------------------- */
const ohneSatz = [...kennungen.keys()].filter((k) => !SAETZE[k]);
/* ---------------------------------------------------------------------
WO STEHT DER SATZ SONST? (08.10.2026)
Ohne diese Zeilen meldete die Prüfung „27 Kennungen ohne Satz" —
und schickte in die falsche Richtung: Neun hatten sehr wohl einen
Satz, nur in einer privaten Tabelle in `manager-ziele.js`. Wer das
liest, schreibt 27 neue Sätze und hat danach DREI Tabellen.
Ein Befund, der die halbe Wahrheit sagt, ist teurer als einer, der
nichts sagt.
------------------------------------------------------------------- */
const NAMENSZEICHEN = "abcdefghijklmnopqrstuvwxyz0123456789_$";
const woSonst = (k) => {
for (const f of skripte) {
if (f === "meldung.js") continue;
const text = ohneKommentareJs(readFileSync(join(JS, f), "utf8"));
const flach = text.split(" ").join("").split("\t").join("");
for (const auf of ["'", '"', "`"]) {
const nadel = k + ":" + auf;
let i = -1;
while ((i = flach.indexOf(nadel, i + 1)) !== -1) {
/* ECHTE WORTGRENZE. Ohne sie trifft `unbekannt` mitten in
`buddy_unbekannt` — beim Messen am 08.10.2026 genau so
passiert, und der Befund sah richtig aus. */
const davor = i === 0 ? "" : flach[i - 1].toLowerCase();
if (!NAMENSZEICHEN.includes(davor)) return f;
}
}
}
return null;
};
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(", ")}` : "");
ohneSatz.length
? `ohne Satz: ${ohneSatz.map((k) => {
const sonst = woSonst(k);
return `${k} (${kennungen.get(k)}`
+ (sonst ? ` — ein Satz steht in ${sonst}, er gehört nach meldung.js` : "")
+ ")";
}).join(", ")}`
: "");
/* Die Zahl gehört in die BEDINGUNG, nicht nur in den Meldetext.
`every()` auf einem leeren Feld liefert `true` — eine Prüfung, die
@@ -158,7 +238,6 @@ 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
@@ -195,6 +274,137 @@ 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?"));
/* ---------------------------------------------------------------------
Jedes Objektliteral einer Datei — über Klammerzählung.
NICHT ÜBERSPRINGEN, wenn eines gefunden ist: Der erste Treffer in
einer Browserdatei ist der ganze Rumpf der Funktion, in der alles
steckt. Wer danach weiterspringt, sieht nie eine Tabelle IM Rumpf.
Gemessen am 08.10.2026: mit Übersprung 0 Treffer, obwohl zwei
Tabellen dastanden. Der Rumpf selbst fällt unten durch, weil er
Klammern enthält.
------------------------------------------------------------------- */
function literale(text) {
const raus = [];
for (let i = 0; i < text.length; i++) {
if (text[i] !== "{") continue;
let tiefe = 0, ende = -1;
for (let j = i; j < text.length; j++) {
if (text[j] === "{") tiefe++;
else if (text[j] === "}") { tiefe--; if (tiefe === 0) { ende = j; break; } }
}
if (ende >= 0) raus.push(text.slice(i, ende + 1));
}
return raus;
}
/* ---------------------------------------------------------------------
Ist dieses Literal eine reine Zuordnung Kennung → Satz?
WARUM DIE REGEL SO ENG IST. Ein weicherer Sucher („irgendwo steht
ein Name mit einem Satz dahinter") meldete am 08.10.2026 vier
Treffer, von denen zwei Fehlalarme waren: `leer:` in den
Spaltenlisten von `aufgaben.js` und `chat.js` — Beschriftungen
einer leeren Spalte, zufällig mit einem Namen, den es auch als
Serverkennung gibt. Ein dritter Treffer, `reaktion.js`, war
ebenfalls keiner: Dort steht kein Satz zu einer Absage, sondern
ein Platzhaltertext für das Eingabefeld, und sein Schlüssel kommt
nicht aus `fehler:`, sondern aus `schreib_grund`.
Verlangt wird deshalb: jeder Wert ist eine Zeichenkette, im
Literal steht nichts anderes, und JEDER Schlüssel ist eine
Serverkennung. Damit fällt eine Satzliste auf und ein Datensatz
nicht. Nachgemessen über alle Browserdateien: genau ein Treffer.
------------------------------------------------------------------- */
function nurSaetze(lit) {
const inhalt = lit.slice(1, -1);
if (/[{}()]/.test(inhalt)) return null;
/* Alle drei Anführungsarten — sonst ist die Prüfung blind, sobald
jemand doppelte statt einfacher benutzt. */
const found = [...inhalt.matchAll(
/([a-z][a-z0-9_]*)\s*:\s*(?:'([^']*)'|"([^"]*)"|`([^`]*)`)/g)]
.map((m) => [m[1], m[2] ?? m[3] ?? m[4]]);
if (!found.length) return null;
/* Die Zahl der Schlüsselzeilen muss zur Zahl der Paare passen —
sonst steht im Literal noch etwas, das kein Satz ist. */
const zeilen = (inhalt.match(/^\s*[a-z][a-z0-9_]*\s*:/gm) || []).length;
if (zeilen !== found.length) return null;
return new Map(found);
}
/** Wo außerhalb von meldung.js steht eine Satztabelle für Kennungen? */
function zweittabellen() {
const raus = [];
for (const f of skripte) {
if (f === "meldung.js") continue;
const text = ohneKommentareJs(readFileSync(join(JS, f), "utf8"));
for (const lit of literale(text)) {
const m = nurSaetze(lit);
if (!m || m.size < 2) continue;
if (![...m.keys()].every((k) => kennungen.has(k))) continue;
raus.push({ datei: f, keys: [...m.keys()] });
}
}
return raus;
}
/* ---- 4. EINE Tabelle, nicht zwei ------------------------------------
DER FALL, DER DAZU GEFÜHRT HAT (08.10.2026). `manager-ziele.js`
führte eine eigene Tabelle mit zwölf Sätzen. Darüber stand als
Begründung wörtlich: „An EINER Stelle: Stünde jeder dort, wo er
gebraucht wird, hieße dieselbe Absage an zwei Stellen
verschieden." Der Satz war richtig — nur war diese Tabelle selbst
inzwischen die zweite Stelle.
GEMESSEN, was das gekostet hat: Neun Kennungen hatten ihren Satz
NUR dort; auf jeder anderen Seite, die dieselbe Absage bekam,
erschien der Ersatzsatz. Drei standen in BEIDEN Tabellen mit
verschiedenem Wortlaut. Und zwei Sätze der Haustabelle waren für
eine fremde Seite geschrieben („Karte", „Stück") — sie wären
beim Auflösen auf einer Seite erschienen, auf der es weder Karte
noch Stück gibt. Die Pflicht ist deshalb doppelt: EINE Tabelle,
und jeder Satz darin muss auf jeder Seite wahr sein, die ihn
auslösen kann.
AUSNAHMEN WERDEN BENANNT, nicht stillschweigend zugelassen — und
die Gegenwache darunter prüft, dass jede Ausnahme noch existiert.
Eine Ausnahmeliste, die niemand pflegt, ist dieselbe Falle wie
eine abgeschriebene Liste (11.09.2026).
------------------------------------------------------------------- */
const ERLAUBT = new Map([
["entwicklung.js", {
keys: ["kein_hakt", "schon_da"],
grund: "zwei Absagen am Schritt-Knopf, absichtlich knapper und auf "
+ "die Handlung bezogen; die Haustabelle sagt dasselbe allgemein",
}],
]);
const zweite = zweittabellen();
const unerlaubt = zweite.filter((z) => {
const e = ERLAUBT.get(z.datei);
return !e || z.keys.some((k) => !e.keys.includes(k));
});
pruef("Nur die Haustabelle übersetzt Server-Kennungen", unerlaubt.length === 0,
unerlaubt.length
? unerlaubt.map((z) => `${z.datei} führt eigene Sätze für `
+ `${z.keys.join(", ")} — sie gehören nach meldung.js`).join("; ")
: "");
/* Gegenwache zur Ausnahmeliste: Jede benannte Ausnahme muss es noch
geben. Sonst bleibt ein Freibrief stehen, nachdem die Stelle längst
aufgeräumt ist — und deckt beim nächsten Mal etwas ab, das niemand
entschieden hat. */
const toteAusnahmen = [...ERLAUBT.keys()].filter(
(f) => !zweite.some((z) => z.datei === f));
pruef("Jede benannte Ausnahme gibt es wirklich noch",
ERLAUBT.size > 0 && toteAusnahmen.length === 0,
toteAusnahmen.length ? `nicht mehr vorhanden: ${toteAusnahmen.join(", ")}` : "");
for (const [f, e] of ERLAUBT) {
/* Eine Ausnahme, die niemand liest, wird zur Gewohnheit. */
console.log(` (erlaubt: ${f} für ${e.keys.join(", ")} — ${e.grund})`);
}
/* ---- 3. Lesbar ------------------------------------------------------ */
const schlecht = [];
for (const [k, satz] of Object.entries(SAETZE)) {