From ea8ceaa5398f1f4051167ba7a7ebd0713230f2fd Mon Sep 17 00:00:00 2001 From: Dogfather Date: Mon, 21 Sep 2026 13:00:01 +0200 Subject: [PATCH] Das Farbhaus von Team Dogi lag oeffentlich im Netz Ausgeloest von pruef-modi-wortleck, die seit Tagen rot war: "24 Fundstellen -- der verborgene Zugang waere damit auffindbar." Die erste Frage war, ob sie recht hat. Gemessen: ja, und schlimmer als gedacht. WER DIESE DATEIEN BEKOMMT: Nicht nur Kollegen. `curl` ohne jeden Keks auf workspace.dogfather-universe.com liefert die Skripte und Stilvorlagen des Arbeitsplatzes mit HTTP 200 -- fuer jeden im Netz lesbar. DARUNTER crew-haus.css, 27 806 Byte, das ganze Farbhaus von Team Dogi samt seiner Kommentare. In deren Kopf stand seit dem 10.09. woertlich "Diese Datei wird NUR auf crew.dogfather-universe.com ausgeliefert." Das war eine Behauptung, keine Tatsache: Die Weiche biegt `haus.css` auf diesen Namen um, wer den Namen direkt nannte, ging an ihr vorbei. Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht. Zwei Stellen im Haus haben sich damit widersprochen -- die Datei sagte "nur auf crew.", pruef-crew-adresse verlangte ausdruecklich das Gegenteil ("sie ist kein Geheimnis"). Durchgesetzt war die offene Fassung. Filipe hat am 21.09. entschieden: zusperren. Die alte Begruendung dagegen war die Sorge, eine Sperre koenne die Pruefung blind machen -- die ist jetzt beantwortet statt vermieden: Jeder Eintrag der Tafel wird in BEIDE Richtungen gemessen, und dass die Umbiegung von `haus.css` auf crew. weiterhin greift, ebenfalls. DIE ADRESSE WAR DAS EIGENTLICHE GEHEIMNIS, nicht das Wort. Die Rollennamen stehen auf der Zugangswand von Team Dogi ohnehin offen -- so gewollt. WO sie liegt, stand dagegen im Klartext in gate.css, gate.js und crew-haus.css, alle drei oeffentlich abrufbar. Jetzt steht sie in keiner ausgelieferten Datei mehr. DIE PRUEFUNG SELBST HATTE DREI MAENGEL: 1. Sie kannte die Mehrzahl nicht. `\bmodi\b` fand "ein Modi", nicht "die Modis" -- 23 weitere Stellen, darunter der Satz "Fuer die rechte Hand und die Modis gibt es dort den stillen Zugang". Also genau die Auskunft, die sie verhindern soll, in der Form, die sie nicht sah. 2. Ihre Ausnahmeliste war abgeschrieben. Sie nannte eine Datei; die zweite, die der Server auf crew. begrenzt, haette sie nie erfahren. Ausgenommen ist jetzt genau das, was der Server auch durchsetzt (GEHOERT_ZU_ADRESSE) -- kein Muster, sondern die Durchsetzung selbst. 3. Sie mahnte 45 Kommentare an. Filipes Entscheidung: in Kommentaren ja, in Code und sichtbaren Texten nein. Rot, das immer da ist, wird ueberlesen -- und dann faengt sie auch den echten Fall nicht. Dafuer neu: helfer-ohne-kommentar.mjs, ein Zerleger, der Kommentare zeichengenau durch Leerzeichen ersetzt, ohne Zeichenketten, Vorlagen oder Suchmuster zu beschaedigen. KEIN Suchausdruck -- genau daran ist am 20.09. die Portumstellung gescheitert, mit gueltigem JavaScript ohne das Feld `port`. Dreizehn Proben in beide Richtungen beweisen, dass er weder zu viel noch zu wenig wegnimmt; ein Zerleger, der zu viel nimmt, macht die ganze Pruefung STILL gruen. Von 45 blieben damit drei echte, alle behoben: - daten.modis -> daten.leute (Feldname in der Auskunft, also Code) - zwei Saetze, die Menschen lesen, auf "die Moderation" umgestellt -- fuer jemanden, der neu im Treff ist, sogar klarer Nebenbei, beim selben Durchgang gefunden und geschlossen: - teamlage.js fiel auf einen Rollennamen zurueck. Nachgemessen: Die Stilvorlage kennt zu dieser Karte gar keine solche Regel -- der Rueckfall hat nie etwas bewirkt und nur das Wort ausgeliefert. - werdegang.js fuehrte die Saetze ueber den Rollengruppen selbst, mit den Rollennamen als Schluessel. Der Kommentar daneben behauptete sogar, sie seien "abgeleitet, nicht abgeschrieben". Sie kommen jetzt vom Server, und pruef-werdegang misst, dass jede Gruppe ihren eigenen behaelt. pruef-modi-wortleck 8/0 (war 5, davon 1 rot) pruef-crew-adresse 144/0 (war 135) pruef-werdegang 98 statt 95 Pruefungen, dieselben 3 alten Fehler pruef-team-ampel 32/0, pruef-team-stufen 28/0, pruef-hilfe 84/0 Co-Authored-By: Claude Opus 5 --- server/crew-adresse.js | 46 +++++- server/helfer-ohne-kommentar.mjs | 249 +++++++++++++++++++++++++++++ server/pruef-crew-adresse.mjs | 95 ++++++++++- server/pruef-modi-checkliste.mjs | 2 +- server/pruef-modi-wortleck.mjs | 127 ++++++++++++++- server/pruef-team-ampel.mjs | 14 +- server/pruef-team-stufen.mjs | 6 +- server/pruef-werdegang.mjs | 34 ++++ server/workspace-teamlage.js | 16 +- server/workspace-werdegang.js | 29 ++++ workspace/anruf-probe.html | 8 +- workspace/app.webmanifest | 8 +- workspace/assets/css/crew-haus.css | 20 ++- workspace/assets/css/gate.css | 16 +- workspace/assets/js/gate.js | 9 +- workspace/assets/js/hilfe.js | 11 +- workspace/assets/js/teamlage.js | 24 ++- workspace/assets/js/werdegang.js | 30 +++- workspace/aufgaben.html | 30 ++-- workspace/automation.html | 30 ++-- workspace/befinden.html | 28 ++-- workspace/bereich.html | 38 ++--- workspace/bewerben.html | 34 ++-- workspace/bewerbungen.html | 32 ++-- workspace/calls.html | 32 ++-- workspace/chat.html | 34 ++-- workspace/content.html | 32 ++-- workspace/crew-index.html | 10 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 32 ++-- workspace/entwicklung.html | 28 ++-- workspace/hilfe.html | 30 ++-- workspace/index.html | 8 +- workspace/kalender.html | 32 ++-- workspace/leistung.html | 30 ++-- workspace/personen.html | 30 ++-- workspace/profil.html | 34 ++-- workspace/rechte.html | 28 ++-- workspace/report.html | 32 ++-- workspace/scouting.html | 30 ++-- workspace/start.html | 28 ++-- workspace/startcheck.html | 32 ++-- workspace/steckbrief.html | 34 ++-- workspace/talente.html | 30 ++-- workspace/team.html | 28 ++-- workspace/teamlage.html | 28 ++-- workspace/teilen.html | 26 +-- workspace/treff-moderation.html | 28 ++-- workspace/treff-regeln.html | 28 ++-- workspace/uebersicht.html | 30 ++-- workspace/unsere-seiten.html | 24 +-- workspace/werdegang.html | 28 ++-- workspace/wissen.html | 32 ++-- 53 files changed, 1190 insertions(+), 552 deletions(-) create mode 100644 server/helfer-ohne-kommentar.mjs diff --git a/server/crew-adresse.js b/server/crew-adresse.js index ade8c7f0..ff753e65 100644 --- a/server/crew-adresse.js +++ b/server/crew-adresse.js @@ -214,12 +214,46 @@ export function istWorkspacePfad(pfad) { die jede Wand im Browser ansehen, keine mehr oeffnen -- sie blieben gruen und wuerden nichts mehr messen. Ein gruener Lauf, der nichts mehr prueft, ist schlimmer als ein roter. */ -/* EINE benannte Wand, seit der Treff keine eigene mehr hat. Die Tafel - bleibt trotzdem eine Tafel und wird nicht wieder zum Sonderfall: Sie - ist der Ort, an dem die naechste Wand eingetragen wird, ohne dass - jemand drei Bedingungen umbauen muss. */ -const BENANNTE_WAENDE = [ +/* Die Tafel bleibt eine Tafel und wird nicht wieder zum Sonderfall: + Sie ist der Ort, an dem der naechste Eintrag dazukommt, ohne dass + jemand drei Bedingungen umbauen muss. + + --------------------------------------------------------------------- + SEIT DEM 21.09.2026 STEHT HIER AUCH EINE STILVORLAGE + + Im Kopf von crew-haus.css stand seit dem 10.09.: "Diese Datei wird + NUR auf crew.dogfather-universe.com ausgeliefert." Nachgemessen war + das falsch -- 27 806 Byte, HTTP 200, von der Agenturadresse, OHNE + Anmeldung. Die Weiche biegt `haus.css` auf diesen Namen um; wer den + Namen direkt nennt, kam an der Weiche vorbei. + + Es war also wieder derselbe Befund: Ein Kommentar, der eine Absicht + beschreibt, erfuellt sie nicht. Und es war dieselbe Luecke wie bei + der Zugangswand am 10.09. -- express.static liefert jede Datei aus, + die man beim Namen nennt. + + WAS DARIN STAND: das ganze Farbhaus von Team Dogi, mit seinen + Kommentaren -- darunter zweimal die Crew-Adresse im Klartext. Die + Adresse ist das eigentlich Verborgene; die Rollennamen stehen auf + der Zugangswand dort ohnehin offen (so gewollt: "3 rollen. + dogfather. rechte hand und modis"). Wer die Adresse hat, sieht sie. + Wer sie nicht hat, sollte sie nicht aus einer Stilvorlage der + Agenturseite erfahren. + + DIE INTERNE UMBIEGUNG BLEIBT UNBERUEHRT, und das ist der Grund, + warum dieser Eintrag ueberhaupt funktioniert: Diese Tafel wird in + crewWeiche() ganz oben gefragt (Zeile ~323), die Umbiegung von + `haus.css` passiert weiter unten (~454). Auf crew. wird also nach + wie vor umgebogen -- dort ist die Datei zuhause --, und auf der + Agenturadresse gibt es sie beim Namen nicht mehr. + + Nachgeprueft in pruef-crew-adresse: einmal, dass sie auf crew. + ankommt, und einmal, dass die Agenturadresse 404 sagt. Ohne die + zweite Haelfte waere der Eintrag eine Behauptung wie die, die er + ersetzt. */ +export const GEHOERT_ZU_ADRESSE = [ { datei: "/workspace/crew-index.html", zuhause: istCrewAdresse }, + { datei: "/workspace/assets/css/crew-haus.css", zuhause: istCrewAdresse }, ]; /** Wird hier eine Wand verlangt, die zu einer ANDEREN Tuer gehoert? */ @@ -229,7 +263,7 @@ export function istFremdeWand(hostKopfzeile, pfad) { zieht sie danach selbst zusammen und liefert die Datei aus. Genau daran ist am 04.09.2026 die Schranke des Workspace vorbeigelaufen. */ const p = String(pfad || "").toLowerCase().replace(/\/{2,}/g, "/"); - const wand = BENANNTE_WAENDE.find((w) => w.datei === p); + const wand = GEHOERT_ZU_ADRESSE.find((w) => w.datei === p); if (!wand) return false; if (istPruefAdresse(hostKopfzeile)) return false; const benannt = istOhneModiAdresse(hostKopfzeile) || istCrewAdresse(hostKopfzeile); diff --git a/server/helfer-ohne-kommentar.mjs b/server/helfer-ohne-kommentar.mjs new file mode 100644 index 00000000..ee89c47b --- /dev/null +++ b/server/helfer-ohne-kommentar.mjs @@ -0,0 +1,249 @@ +/* ===================================================================== + helfer-ohne-kommentar.mjs — Kommentare entfernen, ohne den Rest zu + beschaedigen (21.09.2026) + + --------------------------------------------------------------------- + WOFUER + + Filipes Entscheidung vom 21.09.2026 zum Rollennamen der Community: + Er darf nicht in CODE und nicht in SICHTBAREN TEXTEN stehen -- in + Kommentaren schon. Die Begruendung ist messbar: Auf der Zugangswand + von Team Dogi stehen die drei Rollennamen ohnehin offen ("3 rollen. + dogfather. rechte hand und modis"), und geschuetzt wird der Zugang + durch den Code, nicht durch ein Wort. Eine Pruefung, die 45 + Kommentare anmahnt, waere eine Dauer-Rotmeldung -- und Rot, das + immer da ist, wird ueberlesen. + + Damit diese Regel ueberhaupt pruefbar ist, muss jemand sagen + koennen, was Kommentar ist und was nicht. Genau das tut diese Datei. + + --------------------------------------------------------------------- + WARUM KEIN EINFACHER SUCHEN-UND-ERSETZEN-AUSDRUCK + + Ein Muster, das einfach vom naechsten Sternchen-Schraegstrich bis + zum uebernaechsten loescht, sieht richtig aus und ist es nicht. + Es zerschneidet zum Beispiel eine Adresse, in der dieselbe + Zeichenfolge mitten in einer Zeichenkette steht, oder ein + Suchmuster, das Schraegstrich und Sternchen als Zeichen + enthaelt -- und macht aus gueltigem Programmtext Unsinn. + + (Beide Beispiele stehen hier bewusst NICHT ausgeschrieben: Ein + Kommentar, der die Zeichenfolge enthaelt, mit der ein Kommentar + endet, beendet sich selbst. Beim ersten Versuch ist genau das + passiert -- in der Datei, die davor warnt.) + + IM HAUS IST DIESE FALLE SCHON EINMAL ZUGESCHNAPPT: Am 20.09.2026 hat ein Ausdruck beim + Umstellen der Portnummern `", port: 4231, path: "` fuer eine + Zeichenkette gehalten. Das Ergebnis war gueltiges JavaScript ohne + das Feld `port` -- `node --check` sagte bei allen 164 Dateien "in + Ordnung", und aufgefallen ist es erst, weil ein Lauf gegen einen + GEMESSENEN Vorstand verglichen wurde. + + Deshalb wird hier Zeichen fuer Zeichen gelesen und mitgefuehrt, wo + man gerade steht: in Programmtext, in einer Zeichenkette, in einer + Vorlage, in einem Suchmuster oder in einem Kommentar. + + --------------------------------------------------------------------- + DAS SCHWIERIGE STUECK: IST `/` EIN TEILEN ODER EIN SUCHMUSTER + + `a / b` teilt, `/a/.test(x)` sucht. Unterscheiden kann man das nur + am ZEICHEN DAVOR: Nach einem Wert (Zahl, Name, schliessende Klammer) + teilt es; nach einem Operator, einem Komma oder einer oeffnenden + Klammer beginnt ein Suchmuster. Das ist die Regel, die auch + Browser benutzen, und sie ist fuer diesen Zweck genau genug. + + WAS SIE NICHT KANN: `return /x/` gegen `a++ /x/` unterscheiden, + wenn davor ein Schluesselwort steht, das wie ein Name aussieht. + Deshalb gelten `return`, `typeof`, `case`, `in`, `of`, `new`, + `delete`, `void`, `do` und `else` ausdruecklich als Operator. Die + Liste steht unten und ist abgezaehlt, nicht geraten. + + --------------------------------------------------------------------- + WAS ZURUECKKOMMT + + Derselbe Text, gleich lang, mit Leerzeichen an den Stellen, an denen + Kommentar stand. GLEICH LANG ist Absicht: So stimmen Zeilennummern + und Spalten weiterhin, und ein Fund laesst sich ohne Umrechnung in + der Originaldatei zeigen. Zeilenumbrueche bleiben deshalb erhalten. + ===================================================================== */ + +/* Nach diesen Woertern beginnt ein `/` ein Suchmuster und kein Teilen. + Abgezaehlt, nicht geraten -- siehe Kopf. */ +const VOR_SUCHMUSTER = new Set([ + "return", "typeof", "case", "in", "of", "new", "delete", "void", + "do", "else", "yield", "await", "instanceof", +]); + +/** Ersetzt jeden Kommentar in JavaScript durch Leerzeichen. + * + * Zeichenketten, Vorlagen und Suchmuster bleiben unangetastet -- + * auch wenn `//` oder darin steht. */ +export function jsOhneKommentar(quelle) { + const t = String(quelle); + let aus = ""; + let i = 0; + /* Das letzte Zeichen, das kein Leerraum war. Daran haengt die + Entscheidung Teilen-oder-Suchmuster. */ + let letztes = ""; + let letztesWort = ""; + + const schreibe = (s) => { aus += s; }; + const leeren = (s) => { aus += s.replace(/[^\n]/g, " "); }; + + while (i < t.length) { + const c = t[i]; + const d = t[i + 1]; + + /* ---- Kommentare ------------------------------------------------ */ + if (c === "/" && d === "/") { + let j = i; + while (j < t.length && t[j] !== "\n") j++; + leeren(t.slice(i, j)); + i = j; + continue; + } + if (c === "/" && d === "*") { + let j = t.indexOf("*/", i + 2); + j = j === -1 ? t.length : j + 2; + leeren(t.slice(i, j)); + i = j; + continue; + } + + /* ---- Zeichenketten --------------------------------------------- */ + if (c === '"' || c === "'" || c === "`") { + let j = i + 1; + while (j < t.length) { + if (t[j] === "\\") { j += 2; continue; } + if (t[j] === c) { j++; break; } + /* In einer Vorlage kann `${…}` wieder Programmtext sein. Fuer + den Zweck hier -- Kommentare finden -- reicht es, das Stueck + als Ganzes stehen zu lassen: Ein Kommentar darin waere + ohnehin Programmtext und soll nicht verschwinden. */ + j++; + } + schreibe(t.slice(i, j)); + letztes = c; letztesWort = ""; + i = j; + continue; + } + + /* ---- Suchmuster ------------------------------------------------ */ + if (c === "/") { + const wertDavor = /[\w$)\]]/.test(letztes) && !VOR_SUCHMUSTER.has(letztesWort); + if (!wertDavor) { + let j = i + 1; + let inKlasse = false; + let fertig = false; + while (j < t.length) { + const z = t[j]; + if (z === "\\") { j += 2; continue; } + if (z === "\n") break; /* unfertig -- dann war es Teilen */ + if (z === "[") inKlasse = true; + else if (z === "]") inKlasse = false; + else if (z === "/" && !inKlasse) { j++; fertig = true; break; } + j++; + } + if (fertig) { + while (j < t.length && /[a-z]/.test(t[j])) j++; /* Flaggen */ + schreibe(t.slice(i, j)); + letztes = "/"; letztesWort = ""; + i = j; + continue; + } + } + } + + /* ---- alles andere ---------------------------------------------- */ + schreibe(c); + if (!/\s/.test(c)) { + letztes = c; + if (/[\w$]/.test(c)) letztesWort += c; + else letztesWort = ""; + } + i++; + } + return aus; +} + +/** CSS kennt nur die Block-Form und Zeichenketten. */ +export function cssOhneKommentar(quelle) { + const t = String(quelle); + let aus = ""; + let i = 0; + while (i < t.length) { + const c = t[i]; + if (c === "/" && t[i + 1] === "*") { + let j = t.indexOf("*/", i + 2); + j = j === -1 ? t.length : j + 2; + aus += t.slice(i, j).replace(/[^\n]/g, " "); + i = j; + continue; + } + if (c === '"' || c === "'") { + let j = i + 1; + while (j < t.length) { + if (t[j] === "\\") { j += 2; continue; } + if (t[j] === c) { j++; break; } + j++; + } + aus += t.slice(i, j); + i = j; + continue; + } + aus += c; + i++; + } + return aus; +} + +/** HTML: heraus, und die eingebetteten ", endung: ".html", soll: false, satz: "ein Kommentar IM eingebetteten Skript auch nicht" }, + { text: "", endung: ".html", soll: true, satz: "eine Zeichenkette im eingebetteten Skript schon" }, + { text: "", endung: ".html", soll: false, satz: "und im eingebetteten Stil ebenso wenig" }, + ]; + let richtig = 0; + for (const pr of PROBEN) { + const rest = ohneKommentar(pr.text, pr.endung); + const gefunden = MUSTER.test(rest); + if (gefunden === pr.soll) richtig++; + else ok(false, `${pr.satz} -- ABER die Probe lief falsch: ${JSON.stringify(pr.text)}`); + } + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG. "alle richtig" waere auch bei + einer leeren Liste wahr. */ + ok(richtig === PROBEN.length && PROBEN.length === 13, + `alle ${richtig} von ${PROBEN.length} Proben treffen zu`); + + /* Und die Laenge bleibt gleich -- daran haengen die Zeilennummern, + mit denen ein Fund oben gemeldet wird. Waere sie es nicht, zeigte + die Meldung auf die falsche Zeile, und wer nachsieht, findet + nichts und glaubt der Pruefung nicht mehr. */ + const roh = "var a = 1; /* Modi */\nvar b = 2; // Modi\nvar c = 3;"; + const ohne = ohneKommentar(roh, ".js"); + ok(ohne.length === roh.length, + `der Text bleibt zeichengenau gleich lang (${ohne.length} von ${roh.length})`); + ok(ohne.split("\n").length === roh.split("\n").length, + `und behaelt seine ${roh.split("\n").length} Zeilen`); +} + console.log(`\n${fehler ? "FEHLGESCHLAGEN" : "BESTANDEN"} -- ${geprueft} geprueft, ${fehler} Fehler`); process.exit(fehler ? 1 : 0); diff --git a/server/pruef-team-ampel.mjs b/server/pruef-team-ampel.mjs index 3964ecad..f29be1e7 100644 --- a/server/pruef-team-ampel.mjs +++ b/server/pruef-team-ampel.mjs @@ -129,7 +129,7 @@ const antwort = await holeLage(keksDogi); ok(antwort.status === 200, `die Lage kommt (${antwort.status})`); const roh = await antwort.text(); const lage = JSON.parse(roh); -const leute = lage.modis || []; +const leute = lage.leute || []; /* ===================================================================== 1. KOMMT KLARTEXT DURCH? @@ -196,11 +196,11 @@ console.log("--- Die Grenze bei drei Stunden ---"); const idGrenze = anlegen("Grete", "modi", "CODE-MODI-0004"); termin(idGrenze, 179, 10); -const g1 = ((await (await holeLage(keksDogi)).json()).modis || []).find((m) => m.id === idGrenze); +const g1 = ((await (await holeLage(keksDogi)).json()).leute || []).find((m) => m.id === idGrenze); ok(g1?.verfuegbar?.[0]?.ampel === "teils", `179 Minuten sind "teils" (${g1?.verfuegbar?.[0]?.ampel})`); d.prepare("UPDATE termine SET dauer_min = 180 WHERE creator_id = ?").run(idGrenze); -const g2 = ((await (await holeLage(keksDogi)).json()).modis || []).find((m) => m.id === idGrenze); +const g2 = ((await (await holeLage(keksDogi)).json()).leute || []).find((m) => m.id === idGrenze); ok(g2?.verfuegbar?.[0]?.ampel === "voll", `180 Minuten sind "voll" (${g2?.verfuegbar?.[0]?.ampel})`); /* ===================================================================== @@ -218,7 +218,7 @@ for (const id of [idFrei, idTeils, idVoll, idGrenze]) { const nachRegel = d.prepare( `SELECT COUNT(*) AS n FROM termine t WHERE (${regel.wo}) AND date(t.beginn) = ?`) .get(...regel.werte, heute)?.n || 0; - const lage2 = ((await (await holeLage(keksDogi)).json()).modis || []).find((m) => m.id === id); + const lage2 = ((await (await holeLage(keksDogi)).json()).leute || []).find((m) => m.id === id); const nachAmpel = lage2?.verfuegbar?.[0]?.termine ?? -1; if (nachRegel === nachAmpel) gleich++; else ungleich++; } @@ -234,7 +234,7 @@ console.log("--- Wer die Ampel sieht ---"); const handLage = await holeLage(keksHand); ok(handLage.status === 200, `die rechte Hand sieht die Lage (${handLage.status})`); const handRoh = await handLage.text(); -ok(JSON.parse(handRoh).modis?.[0]?.verfuegbar?.length === 7, +ok(JSON.parse(handRoh).leute?.[0]?.verfuegbar?.length === 7, "und die Ampel gehoert dazu -- sie koordiniert das Team"); for (const wort of GEHEIM) { ok(!handRoh.includes(wort), `auch bei ihr steht "${wort.slice(0, 18)}…" nicht drin`); @@ -251,12 +251,12 @@ console.log(""); console.log("--- Gegenprobe: ein Termin veraendert die Farbe ---"); termin(idFrei, 300, 9); -const nachher = ((await (await holeLage(keksDogi)).json()).modis || []).find((m) => m.id === idFrei); +const nachher = ((await (await holeLage(keksDogi)).json()).leute || []).find((m) => m.id === idFrei); ok(nachher?.verfuegbar?.[0]?.ampel === "voll", `aus "frei" wird "voll" (${nachher?.verfuegbar?.[0]?.ampel})`); d.prepare("DELETE FROM termine WHERE creator_id = ?").run(idFrei); -const zurueck = ((await (await holeLage(keksDogi)).json()).modis || []).find((m) => m.id === idFrei); +const zurueck = ((await (await holeLage(keksDogi)).json()).leute || []).find((m) => m.id === idFrei); ok(zurueck?.verfuegbar?.[0]?.ampel === "frei", `und zurueckgenommen wieder "frei" (${zurueck?.verfuegbar?.[0]?.ampel})`); diff --git a/server/pruef-team-stufen.mjs b/server/pruef-team-stufen.mjs index cdc8e5fc..7f04c8a4 100644 --- a/server/pruef-team-stufen.mjs +++ b/server/pruef-team-stufen.mjs @@ -118,7 +118,7 @@ console.log("--- Die Mitglieder-Kachel (Kapitel 4) ---"); const a1 = await holeLage(keksDogi); ok(a1.status === 200, `DogFather bekommt die Lage (${a1.status})`); const lage = await a1.json(); -const leute = lage.modis || []; +const leute = lage.leute || []; /* SEIT DEM 11.09.2026 GEHOERT DIE RECHTE HAND DAZU. @@ -236,7 +236,7 @@ console.log(""); console.log("--- Beim naechsten Laden ---"); const a2 = await holeLage(keksDogi); -const marina2 = ((await a2.json()).modis || []).find((m) => m.id === idMarina); +const marina2 = ((await a2.json()).leute || []).find((m) => m.id === idMarina); ok(marina2?.stufe === "senior", `die neue Stufe kommt zurueck (${marina2?.stufe})`); const inDb = d.prepare("SELECT stufe FROM personen WHERE id = ?").get(idMarina); ok(inDb?.stufe === "senior", `und steht in der Datenbank (${inDb?.stufe})`); @@ -261,7 +261,7 @@ console.log("--- Gegenprobe: wieder zurueck ---"); const zurueck = await setzeStufe(keksDogi, idMarina, "standard"); ok(zurueck.status === 200, `zurueckstufen geht (${zurueck.status})`); const a3 = await holeLage(keksDogi); -const marina3 = ((await a3.json()).modis || []).find((m) => m.id === idMarina); +const marina3 = ((await a3.json()).leute || []).find((m) => m.id === idMarina); ok(marina3?.stufe === "standard", `und die Anzeige folgt (${marina3?.stufe}) -- sie bleibt also nicht auf "senior" stehen`); diff --git a/server/pruef-werdegang.mjs b/server/pruef-werdegang.mjs index 46409b8d..b897fb5b 100644 --- a/server/pruef-werdegang.mjs +++ b/server/pruef-werdegang.mjs @@ -509,6 +509,40 @@ melde("=== 10. Die Liste ==="); `alphabetisch (${namen.join(", ")})`); ok(/kein vergleich/i.test(l.hinweis || ""), "und die Seite sagt selbst, dass sie niemanden vergleicht"); + + /* DER SATZ UEBER EINER GRUPPE KOMMT VOM SERVER (neu am 21.09.2026). + + Er stand bis dahin als Liste in werdegang.js -- also in einer + Datei, die jeder im Netz ohne Anmeldung herunterladen kann, mit + den Rollennamen als Schluessel. Verschoben wurde er, weil + Rollennamen der Community dort nicht hingehoeren. + + WARUM DAS HIER GEMESSEN WIRD: Beim Verschieben kann man eine + Erklaerung leicht ganz verlieren -- die Oberflaeche zeigt dann + einfach keine Zeile mehr, ohne Fehlermeldung. Eine Gruppe mit + Ueberschrift und ohne Erklaerung sieht nicht kaputt aus, sie sieht + nur aermer aus. Genau solche Verluste faellt niemandem auf. */ + const mitSatz = l.liste.filter((p) => p.rolle_satz && p.rolle_satz.length > 10); + ok(mitSatz.length === l.liste.length, + `jede Karte bringt den Satz ihrer Gruppe mit (${mitSatz.length} von ${l.liste.length})`); + + /* GEGENPROBE: Er darf nicht fuer alle derselbe sein -- sonst haette + das Verschieben aus zwei Saetzen einen gemacht, und die Pruefung + darueber waere trotzdem gruen. */ + const verschieden = new Set(l.liste.map((p) => p.rolle_satz)).size; + ok(verschieden === new Set(l.liste.map((p) => p.rolle)).size, + `und jede Rolle hat ihren eigenen (${verschieden} Saetze auf ` + + `${new Set(l.liste.map((p) => p.rolle)).size} Rollen)`); + + /* UND DIE OBERFLAECHE DARF IHN NICHT MEHR SELBST KENNEN. Bliebe die + alte Liste dort stehen, waere nichts gewonnen -- der Rollenname + stuende weiter in einer ausgelieferten Datei, und beide Fassungen + wuerden auseinanderlaufen. */ + const { readFileSync } = await import("node:fs"); + const vorne = readFileSync( + new URL("../workspace/assets/js/werdegang.js", import.meta.url), "utf8"); + ok(!/GRUPPENSATZ\s*=/.test(vorne), + "und die Oberflaeche fuehrt keine eigene Liste mehr"); } /* ======================================================================= diff --git a/server/workspace-teamlage.js b/server/workspace-teamlage.js index b8073658..7c17040e 100644 --- a/server/workspace-teamlage.js +++ b/server/workspace-teamlage.js @@ -195,7 +195,7 @@ teamlageRouter.get("/workspace/api/teamlage", (req, res) => { /* `eingang` auch hier -- die Oberflaeche darf nicht raten muessen, ob das Feld fehlt oder leer ist. */ return res.json({ - modis: [], eingang: [], angebote: [], bereiche: GESAMT, heute: heuteLokal(), + leute: [], eingang: [], angebote: [], bereiche: GESAMT, heute: heuteLokal(), }); } const platz = modis.map(() => "?").join(","); @@ -378,7 +378,19 @@ teamlageRouter.get("/workspace/api/teamlage", (req, res) => { undefined", weil eine Rolle in der Browser-Liste vergessen worden war. Einmal geschrieben, einmal geaendert. */ stufen: STUFEN.map((wert) => ({ wert, name: STUFEN_NAME[wert] })), - modis: modis.map((m) => { + /* DER SCHLUESSEL HEISST "leute" UND NICHT MEHR NACH DER ROLLE + (21.09.2026). + + Die Oberflaeche liest ihn in teamlage.js -- also in einer + Datei, die jeder im Netz ohne Anmeldung herunterladen kann. + Damit stand der Rollenname der Community dort im CODE, nicht + nur in einem Kommentar. Filipes Regel vom selben Tag: in + Kommentaren ja, in Code und sichtbaren Texten nein. + + Die oertliche Variable darunter heisst weiter `modis` -- sie + verlaesst diese Datei nie. Umbenannt wurde genau das, was + ueber die Leitung geht. */ + leute: modis.map((m) => { const s = stand.get(m.id) || {}; const je = {}; for (const [b, gesamt] of Object.entries(GESAMT)) { diff --git a/server/workspace-werdegang.js b/server/workspace-werdegang.js index 8029c79a..10d5560c 100644 --- a/server/workspace-werdegang.js +++ b/server/workspace-werdegang.js @@ -124,6 +124,22 @@ import { VERBLASST_NACH_TAGEN, tabellen as entwicklungTabellen, } from "./workspace-entwicklung.js"; +/* WAS UEBER EINER GRUPPE STEHT (hierher geholt am 21.09.2026). + + Vorher stand das in werdegang.js, also in einer ausgelieferten + Datei. Hier steht es richtig: Der Server schickt jeder Person ihren + Satz mit, und wer die Rolle nicht sehen darf, bekommt die Person + ohnehin nicht. + + NUR ZWEI EINTRAEGE, UND DAS IST ABSICHT. Manager, Scouts und + Creator stehen im Werdegang unter ihrem Namen, ohne Erklaerung -- + dort ist die Ueberschrift selbsterklaerend. Ein Satz um des Satzes + willen waere Fuellung. */ +const ROLLEN_SATZ = { + hand: "Koordiniert den Alltag und schaut mit dir zusammen auf das Team.", + modi: "Die Leute, die im Chat und im Live stehen.", +}; + export const werdegangRouter = express.Router(); const TAG = 86400000; @@ -626,6 +642,19 @@ werdegangRouter.get("/workspace/api/werdegang/liste", nurLeitung, (req, res) => nächsten Umbenennen auseinanderfällt. */ rolle: p.rolle, rolle_name: ROLLEN_NAME[p.rolle] ?? p.rolle, + /* DER SATZ UNTER DER UEBERSCHRIFT (21.09.2026). + + Er stand bis heute in werdegang.js -- also in einer Datei, + die JEDER herunterlaedt, der den Arbeitsplatz oeffnet. Mit + den Rollennamen als Schluessel, und einer davon gehoert der + Community. Dieselbe Ueberlegung wie bei ROLLENTEXT in + start.js: Was einen Rollennamen traegt, kommt vom Server. + + EIN FEHLENDER SATZ IST EIN GUELTIGER ZUSTAND. Nicht jede + Rolle braucht eine Erklaerung, und ein erfundener Satz waere + schlechter als keiner -- die Oberflaeche laesst die Zeile + dann einfach weg. */ + rolle_satz: ROLLEN_SATZ[p.rolle] ?? null, gesetzt: stand.length, gesamt: ENTWICKLUNG_FREMD.length, bewegt: bewegung.reduce((s, m) => s + m.vor + m.zurueck + m.neu + m.geklaert, 0), diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 6287c07f..9c98c233 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +