From 04cfb06d33292b9fa0f3fdac1e41eb10a9f1ccce Mon Sep 17 00:00:00 2001 From: Dogfather Date: Thu, 8 Oct 2026 21:15:36 +0200 Subject: [PATCH] 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. --- server/pruef-meldungen.mjs | 222 ++++++++++++++++++++++++++- workspace/anleitung.html | 28 ++-- workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/js/chat.js | 8 +- workspace/assets/js/manager-ziele.js | 58 +++---- workspace/assets/js/meldung.js | 124 ++++++++++++++- workspace/aufgaben.html | 38 ++--- workspace/automation.html | 32 ++-- workspace/befinden.html | 34 ++-- workspace/bereich.html | 48 +++--- workspace/bewerben.html | 38 ++--- workspace/bewerbungen.html | 36 ++--- workspace/buehne.html | 6 +- workspace/calls.html | 34 ++-- workspace/chat.html | 42 ++--- workspace/content.html | 34 ++-- workspace/crew-index.html | 12 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 ++-- workspace/entwicklung.html | 36 ++--- workspace/hilfe.html | 30 ++-- workspace/index.html | 10 +- workspace/kalender.html | 34 ++-- workspace/leistung.html | 32 ++-- workspace/manager-ziele.html | 29 ++-- workspace/material.html | 32 ++-- workspace/notizen.html | 28 ++-- workspace/personen.html | 32 ++-- workspace/profil.html | 36 ++--- workspace/reaktion-foyer.html | 30 ++-- workspace/reaktion.html | 40 ++--- workspace/rechte.html | 34 ++-- workspace/report.html | 36 ++--- workspace/scouting.html | 32 ++-- workspace/start.html | 38 ++--- workspace/startcheck.html | 34 ++-- workspace/steckbrief.html | 36 ++--- workspace/support.html | 28 ++-- workspace/tafel.html | 6 +- workspace/talente.html | 36 ++--- workspace/team.html | 32 ++-- workspace/teamlage.html | 34 ++-- workspace/teilen.html | 30 ++-- workspace/treff-moderation.html | 32 ++-- workspace/treff-regeln.html | 32 ++-- workspace/uebersicht.html | 32 ++-- workspace/unsere-seiten.html | 30 ++-- workspace/unterstuetzen.html | 32 ++-- workspace/werdegang.html | 32 ++-- workspace/willkommen.html | 2 +- workspace/wissen.html | 34 ++-- 52 files changed, 1081 insertions(+), 744 deletions(-) diff --git a/server/pruef-meldungen.mjs b/server/pruef-meldungen.mjs index 763bd37b..4556740c 100644 --- a/server/pruef-meldungen.mjs +++ b/server/pruef-meldungen.mjs @@ -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)) { diff --git a/workspace/anleitung.html b/workspace/anleitung.html index 31f6dfae..7c368954 100644 --- a/workspace/anleitung.html +++ b/workspace/anleitung.html @@ -7,17 +7,17 @@ - - - - - + + + + + - - + + @@ -289,16 +289,16 @@ Und anleitung.js braucht es doppelt: Symbol, Farbe, Name und Adresse jeder Karte kommen von dort, nicht vom Server. --> - + - - - - - - + + + + + + diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 0b97f21e..7aebcdeb 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +