From a7b577526842a3382df624507f46f37ce8738a89 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Tue, 6 Oct 2026 17:44:10 +0200 Subject: [PATCH] =?UTF-8?q?Die=20Rechtetafel=20siebt=20die=20Karten=20jetz?= =?UTF-8?q?t=20wirklich=20--=20und=20=E2=80=9Eab=20Dabei"=20steht=20dran?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ZWEI SACHEN, EINE DAVON EIN ECHTER BEFUND. 1) DIE KARTEN KAMEN AN DER RECHTETAFEL VORBEI Der Bauplan sagt in Kapitel 04 und noch einmal in der Abnahme: "Leite die sichtbaren Karten aus derselben Rechte-Tafel ab, die rechte.html verwendet. Eine Rolle sieht nur Karten zu Seiten, die sie oeffnen darf; pruefe das SERVERSEITIG." Ich habe dafuer eine Pruefung geschrieben, die die Seite ueber die ECHTE Schnittstelle sperrt (nicht mit einem UPDATE in der Datenbank -- nur so laeuft tafelLaden() mit). Sie wurde sofort rot: Nach dem Sperren von bereich.html fuer einen Modi lieferte der Server weiterhin ALLE ELF Bereichs-Karten. Im Browser fiel das nicht auf, und das ist der heikle Teil: Die Seite verbindet jede Karte mit einer Kachel, und die Kachelliste ist gefiltert -- die Karten verschwanden also auf dem Bildschirm. Die TEXTE gingen trotzdem hinaus. Eine Oberflaeche, die etwas nicht anzeigt, ist keine Schranke; genau deshalb steht im Bauplan "serverseitig". Jetzt siebt `darfKarte()` an der Quelle. Der Pfad wird aus dem Schluessel GERECHNET (`bereich:highlight` -> /workspace/bereich.html), nicht aus einer Zuordnungsliste -- das ist die Umkehrung von `schluesselVonZiel`, und eine gepflegte Tabelle daneben waere die, die beim naechsten neuen Brett fehlt. Entschieden wird mit `darfSeite` selbst; hier wird nichts davon nachgebaut. Gegenprobe in derselben Pruefung: zuruecksperren -> die Karten sind wieder da (29 von 29). Und eine Zeile dazwischen, die zaehlt, dass NUR sie verschwunden sind -- waere mit der Sperre die halbe Anleitung weggefallen, waere die erste Zeile auch gruen gewesen. 2) „AB DABEI" UND „AB STAMM" AN DER KARTE (Kapitel 04) "Kann ein Community-Mitglied etwas wegen seiner Stufe noch nicht, steht das an der Karte." Auch das abgeleitet: aus TREFF_SCHREIBEN, derselben Tafel, die das Schreiben ENTSCHEIDET. `null` darin heisst "von aussen schreibt hier niemand" -- das ist keine Stufe, die man erreicht, und bekommt deshalb keine Marke. "ab Stamm" an einem Anschlagbrett waere ein Versprechen, das nie eingeloest wird. Als Wort, nicht als Schloss-Symbol: Ein Schloss sagt "du darfst nicht" und laesst offen, ob jemals. "ab Dabei" sagt, dass es kommt -- und wann, steht mit Datum in der Karte "Deine Stufe" darueber. Ruhig gestaltet, nicht rot: Es ist keine Sperre, sondern eine Auskunft ueber die Zeit. GEPRUEFT (pruef-anleitung 166 -> 173) Die wichtigste der neuen Zeilen ist die, dass die Marke nach dem Aufstieg VERSCHWINDET. Eine Marke, die bleibt, ist schlimmer als keine: Sie sagt jemandem, er duerfe etwas nicht, das er laengst darf, und er probiert es gar nicht erst. Gemessen an drei Karten mit drei verschiedenen Antworten (Chat: Dabei, Highlights: Stamm, Anschlagbrett: gar nichts) -- waere die Rechnung grob falsch, traefe sie alle drei gleich. Dazu die Gegenprobe, dass eine Teamrolle keine einzige solche Marke bekommt. Mitgelaufen: pruef-rechtetafel 19, pruef-rechte-umstellen 56, pruef-css-klassen, pruef-tippziele 13 -- alle gruen. Die Abnahmezahlen aus Kapitel 13 (14/29/29/34 mit ihrer Aufteilung) sind nach dem neuen Sieb unveraendert; das ist der Beleg, dass es im Normalfall nichts wegnimmt. Im Browser nachgemessen (390 px, echte Anmeldung): Community sieht vier Marken (Rudel-Chat, Wunschliste, Mitmachen „ab Dabei", Highlights „ab Stamm"), die rechte Hand keine einzige. Co-Authored-By: Claude Opus 5 --- server/mess-anleitung-crew.mjs | 8 ++- server/pruef-anleitung.mjs | 104 +++++++++++++++++++++++++++++ server/workspace-anleitung.js | 81 +++++++++++++++++++++- workspace/anleitung.html | 28 ++++---- workspace/anruf-probe.html | 10 +-- workspace/app.webmanifest | 8 +-- workspace/assets/css/anleitung.css | 18 +++++ workspace/assets/js/anleitung.js | 12 ++++ workspace/aufgaben.html | 38 +++++------ workspace/automation.html | 32 ++++----- workspace/befinden.html | 34 +++++----- workspace/bereich.html | 44 ++++++------ workspace/bewerben.html | 38 +++++------ workspace/bewerbungen.html | 36 +++++----- workspace/buehne.html | 6 +- workspace/calls.html | 34 +++++----- workspace/chat.html | 40 +++++------ 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 | 28 ++++---- workspace/material.html | 32 ++++----- workspace/notizen.html | 28 ++++---- workspace/personen.html | 32 ++++----- workspace/profil.html | 36 +++++----- workspace/reaktion.html | 38 +++++------ 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/wissen.html | 34 +++++----- 51 files changed, 905 insertions(+), 690 deletions(-) diff --git a/server/mess-anleitung-crew.mjs b/server/mess-anleitung-crew.mjs index e71a35a8..17df9dec 100644 --- a/server/mess-anleitung-crew.mjs +++ b/server/mess-anleitung-crew.mjs @@ -220,8 +220,13 @@ for (const [rolle, wie] of ROLLEN) { const klein = [...document.querySelectorAll("button, a")] .filter((e) => { const r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0 && r.height < 44; }).length; + /* „ab Dabei" / „ab Stamm" -- der Bauplan will sie an der Karte + sehen, nicht nur im Datenstrom. */ + const marken = [...document.querySelectorAll(".an-karte__abstufe")] + .map((m) => `${m.closest(".an-karte")?.querySelector(".an-karte__name") + ?.textContent || "?"}: ${m.textContent}`); return { - karten, quer: breit, klein, + karten, quer: breit, klein, marken, vorweg: z("vorweg-block") && !z("vorweg-block").hidden, stufe: z("stufe-block") && !z("stufe-block").hidden, stufeText: z("stufe-jetzt")?.textContent || "", @@ -236,6 +241,7 @@ for (const [rolle, wie] of ROLLEN) { console.log(` Deine Stufe : ${lage.stufe} ${lage.stufeText}`); console.log(` Querscrollen : ${lage.quer ? "JA -- Befund" : "nein"}`); console.log(` Tippziele unter 44 : ${lage.klein}`); + console.log(` Stufen-Marken : ${lage.marken.join(" | ") || "keine"}`); console.log(` Browsermeldungen : ${klagen.length ? klagen.join(" | ") : "keine"}`); console.log(` Fehlgeschlagen : ${schlecht.length ? schlecht.slice(0, 6).join(" | ") : "nichts"}`); diff --git a/server/pruef-anleitung.mjs b/server/pruef-anleitung.mjs index ea7cc2ff..e40cf475 100644 --- a/server/pruef-anleitung.mjs +++ b/server/pruef-anleitung.mjs @@ -1186,6 +1186,110 @@ melde("\n=== Die Abnahme-Checkliste (Bauplan Kapitel 13) ==="); } } +/* ===================================================================== + „AB DABEI" UND „AB STAMM" AN DER KARTE (Bauplan Kapitel 04) + --------------------------------------------------------------------- + „Kann ein Community-Mitglied etwas wegen seiner Stufe noch nicht, + steht das an der Karte." + + DIE WICHTIGE ZEILE IST DIE DRITTE: Nach dem Aufstieg muss die Marke + VERSCHWINDEN. Eine Marke, die bleibt, ist schlimmer als keine -- + sie sagt jemandem, er duerfe etwas nicht, das er laengst darf, und + er probiert es gar nicht erst. + + GEMESSEN WIRD AN DREI KARTEN MIT DREI VERSCHIEDENEN ANTWORTEN, und + das ist Absicht: Der Rudel-Chat braucht „Dabei", die Highlights + brauchen „Stamm", und das Anschlagbrett braucht gar nichts (dort + schreibt von aussen NIEMAND, auf keiner Stufe). Waere die Rechnung + grob falsch, traefe sie alle drei gleich. */ +melde("\n=== „ab Dabei“ und „ab Stamm“ an der Karte ==="); +{ + const markeVon = (j, kachel) => + (j?.karten || []).find((k) => k.kachel === kachel)?.erst_ab ?? null; + const gastId = (() => { + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const z = d.prepare("SELECT id FROM personen WHERE rolle = 'gast'").get(); + d.close(); return z?.id || 0; + })(); + const beitritt = (tageHer) => { + const d = new DatabaseSync(process.env.WORKSPACE_DB); + d.prepare("UPDATE personen SET erstellt = ? WHERE id = ?") + .run(new Date(Date.now() - tageHer * 86400_000).toISOString(), gastId); + d.close(); + }; + + beitritt(0); + const neuJ = (await holen("gast", CREW_HOST)).j; + ok(markeVon(neuJ, "chat:treff") === "Dabei" + && markeVon(neuJ, "bereich:highlight") === "Stamm" + && markeVon(neuJ, "bereich:anschlag") === null, + `Stufe Neu: Chat „ab ${markeVon(neuJ, "chat:treff")}“, Highlights ` + + `„ab ${markeVon(neuJ, "bereich:highlight")}“, Anschlagbrett ` + + `${markeVon(neuJ, "bereich:anschlag") || "ohne Marke"}`); + + beitritt(10); + const dabeiJ = (await holen("gast", CREW_HOST)).j; + ok(markeVon(dabeiJ, "chat:treff") === null + && markeVon(dabeiJ, "bereich:highlight") === "Stamm", + `Stufe Dabei: die Chat-Marke ist weg (${markeVon(dabeiJ, "chat:treff") || "weg"}), ` + + `die der Highlights bleibt (${markeVon(dabeiJ, "bereich:highlight")})`); + + /* Eine Teamrolle hat keine Mitglieder-Stufe -- an ihren Karten darf + nie eine solche Marke stehen. Ohne diese Zeile waere die Rechnung + auch dann gruen, wenn sie die Stufe gar nicht beachtete. */ + const handJ = (await holen("hand", CREW_HOST)).j; + const beimTeam = (handJ?.karten || []).filter((k) => k.erst_ab).length; + ok(beimTeam === 0, + `eine Teamrolle bekommt keine einzige Stufen-Marke (${beimTeam})`); + beitritt(0); +} + +/* ===================================================================== + EINE SEITE SPERREN — UND DIE KARTE IST WEG (Bauplan Kapitel 13) + --------------------------------------------------------------------- + „In ‚Wer sieht was' eine Seite sperren: Die Karte verschwindet bei + dieser Rolle." + + DAS IST DER PUNKT, AN DEM DIE ANLEITUNG GEFAEHRLICH WERDEN KANN. + Eine Karte ist eine Einladung mit einem Knopf „Hingehen". Bleibt sie + stehen, nachdem die Seite gesperrt wurde, schickt die Einweisung + jemanden gegen eine Tuer — und erzaehlt ihm vorher noch, was dahinter + zu tun waere. Der Bauplan verlangt deshalb ausdruecklich, dass die + Karten aus DERSELBEN Tafel kommen wie die Tuer. + + GESPERRT WIRD UEBER DIE ECHTE SCHNITTSTELLE, nicht mit einem + UPDATE in der Datenbank: Nur so laeuft auch `tafelLaden()` mit, und + genau dort koennte die Kette reissen. Danach wird die Sperre wieder + aufgehoben und nachgemessen, dass die Karte zurueckkommt — sonst + wuesste man nicht, ob sie verschwunden ist oder nur nie da war. */ +melde("\n=== Seite sperren, Karte weg (Abnahme Kapitel 13) ==="); +{ + const PFAD = "/workspace/bereich.html"; + const stellen = (erlaubt) => roh("/workspace/api/rechte/feld", CREW_HOST, + { pfad: PFAD, rolle: "modi", erlaubt }, kekse.admin, "PUT"); + + const vorher = (await holen("modi", CREW_HOST)).j?.karten || []; + const bereicheVorher = vorher.filter((k) => k.kachel.startsWith("bereich:")).length; + ok(bereicheVorher > 0, + `vorher erklaert die Anleitung dem Modi ${bereicheVorher} Bereichs-Karten`); + + const zu = await stellen(false); + const gesperrt = (await holen("modi", CREW_HOST)).j?.karten || []; + const bereicheGesperrt = gesperrt.filter((k) => k.kachel.startsWith("bereich:")).length; + ok(zu.code === 200 && bereicheGesperrt === 0, + `nach dem Sperren sind sie weg (HTTP ${zu.code}, noch ${bereicheGesperrt})`); + /* UND NUR SIE. Waere mit der Sperre die halbe Anleitung + verschwunden, waere die Zeile darueber auch gruen. */ + ok(gesperrt.length === vorher.length - bereicheVorher, + `und nur sie (${vorher.length} - ${bereicheVorher} = ${gesperrt.length})`); + + const auf = await stellen(true); + const zurueck = (await holen("modi", CREW_HOST)).j?.karten || []; + ok(auf.code === 200 && zurueck.length === vorher.length, + `Gegenprobe: zurueckgestellt sind sie wieder da (${zurueck.length} von ` + + `${vorher.length})`); +} + /* ---------- Abschluss ------------------------------------------------ */ try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } console.log(`\n${fehler ? "FEHLER: " + fehler : "Alles in Ordnung"} — ${geprueft} Prüfungen`); diff --git a/server/workspace-anleitung.js b/server/workspace-anleitung.js index 449767ba..8ecd5fce 100644 --- a/server/workspace-anleitung.js +++ b/server/workspace-anleitung.js @@ -34,12 +34,13 @@ import express from "express"; import { db, sitzungLesen, AUSSEN_ROLLEN, bereicheFuer } from "./workspace.js"; +import { darfSeite } from "./rechte.js"; import { STUFEN, PFLEGE_ROLLEN, fassungenFuer, } from "./anleitung-tabellen.js"; import { stufeVon, STUFEN_NAME, DABEI_TAGE, STAMM_TAGE, STAMM_BEITRAEGE, - TREFF_SCHREIBEN, + TREFF_SCHREIBEN, STUFEN as MITGLIED_STUFEN, } from "./workspace-treff.js"; export const anleitungRouter = express.Router(); @@ -96,6 +97,73 @@ function wessenStand(req) { return (req.sicht || req.person)?.id ?? 0; } +/** Darf diese Person die Seite ueberhaupt oeffnen, die die Karte + * erklaert? + * + * Bauplan Kapitel 04 und 13: „Leite die sichtbaren Karten aus + * derselben Rechte-Tafel ab, die rechte.html verwendet. Eine Rolle + * sieht nur Karten zu Seiten, die sie oeffnen darf; pruefe das + * SERVERSEITIG." + * + * GEMESSEN AM 06.10.2026, UND ES STIMMTE NICHT: Wer in „Wer sieht + * was" fuer einen Modi `bereich.html` sperrte, bekam vom Server + * weiterhin alle elf Bereichs-Karten geliefert. Im Browser fielen sie + * zwar weg (die Seite verbindet jede Karte mit einer Kachel, und die + * Kachelliste ist gefiltert) -- aber die TEXTE gingen trotzdem + * hinaus, und eine Oberflaeche ist keine Schranke. Genau deshalb + * steht im Bauplan „serverseitig". + * + * DER PFAD WIRD AUS DEM SCHLUESSEL GERECHNET, nicht aus einer + * Zuordnungsliste: Der Schluessel ist der Seitenname ohne `.html`, + * bei Unterseiten mit einem Doppelpunkt dahinter + * (`bereich:highlight`, `chat:treff`). Das ist die Umkehrung von + * `Bereiche.schluesselVonZiel`; eine gepflegte Tabelle daneben waere + * die, die beim naechsten neuen Brett fehlt. + * + * OHNE EINTRAG IN DER TAFEL HEISST NEIN -- das entscheidet + * `darfSeite` selbst, und zwar fuer das ganze Haus gleich. Hier wird + * nichts davon nachgebaut. */ +function darfKarte(person, schluessel) { + const name = String(schluessel || "").split(":")[0]; + if (!name) return false; + return darfSeite(person, `/workspace/${name}.html`); +} + +/** Kann diese Person das, was die Karte beschreibt, wegen ihrer + * Mitglieder-Stufe noch NICHT? + * + * Bauplan Kapitel 04: "Kann ein Community-Mitglied etwas wegen seiner + * Stufe noch nicht, steht das an der Karte („ab Dabei", „ab Stamm")." + * + * ABGELEITET AUS DER TAFEL, DIE ES ENTSCHEIDET. `TREFF_SCHREIBEN` + * sagt je Brett, welche Stufe zum Schreiben noetig ist -- dieselbe + * Tafel, die der Server beim Schreiben durchsetzt. Eine zweite Liste + * hier waere die, die eines Tages "ab Dabei" an eine Karte schreibt, + * bei der laengst jeder darf (oder umgekehrt, und das waere + * schlimmer: eine Karte ohne Marke, die beim Antippen abweist). + * + * `null` HAT ZWEI BEDEUTUNGEN IN DER TAFEL, und beide fuehren hier + * richtigerweise zu keiner Marke: Ein Brett ohne Eintrag kennt die + * Tafel nicht (dann geht es nicht ums Schreiben), und ein Brett mit + * `null` darf von aussen ueberhaupt niemand beschreiben -- das ist + * keine Stufe, die man erreicht, sondern eine dauerhafte Aufteilung. + * "ab Stamm" daranzuschreiben waere ein Versprechen, das nie + * eingeloest wird. + * + * DER SCHLUESSEL TRAEGT DAS BRETT SCHON IN SICH: `bereich:highlight` + * und `chat:treff` -- beide Formen entstehen in + * `Bereiche.schluesselVonZiel`, und beide tragen hinter dem + * Doppelpunkt genau den Namen, den die Tafel kennt. */ +function erstAbFuer(person, schluessel) { + const hat = stufeVon(person); + if (!hat) return null; // Teamrolle: keine Mitglieder-Stufe + const brett = String(schluessel || "").split(":")[1] || ""; + const noetig = TREFF_SCHREIBEN[brett]; + if (!noetig) return null; + if (MITGLIED_STUFEN.indexOf(hat) >= MITGLIED_STUFEN.indexOf(noetig)) return null; + return STUFEN_NAME[noetig] || noetig; +} + /** „Deine Stufe" — nur fuer die Community (Bauplan, Kapitel 04). * * WARUM DIE ZAHLEN NICHT HIER STEHEN: `DABEI_TAGE` und die beiden @@ -174,7 +242,12 @@ anleitungRouter.get("/workspace/api/anleitung", (req, res) => { const karten = d.prepare( `SELECT id, kachel, stufe, wozu, tust, merke, reihe, geaendert FROM anl_karte WHERE haus = ? AND rolle = ? - ORDER BY reihe, id`).all(hausVon(req.person), rolle); + ORDER BY reihe, id`).all(hausVon(req.person), rolle) + /* DIE RECHTETAFEL SIEBT MIT -- siehe darfKarte(). Gefiltert wird + an der Quelle und nicht erst beim Senden: Darunter haengen der + Zaehler, die Stufen-Abschnitte und die Luecken-Anzeige der + Pflege, und die sollen alle dieselbe Menge meinen. */ + .filter((k) => darfKarte(person, k.kachel)); /* `erledigt` kommt aus derselben Abfrage und nicht aus einer zweiten: Zwei Abfragen koennten zwischen den Zeilen @@ -241,7 +314,9 @@ anleitungRouter.get("/workspace/api/anleitung", (req, res) => { sehen, kann sie aber nicht setzen -- es waere sonst sein eigener Fortschritt an fremden Schritten. */ darf_abhaken: String(req.person?.rolle || "") === rolle, - karten: karten.map((k) => ({ ...k, neu: istNeu(k.geaendert) })), + karten: karten.map((k) => ({ + ...k, neu: istNeu(k.geaendert), erst_ab: erstAbFuer(person, k.kachel), + })), nicht: nachArt("nicht").map((z) => ({ id: z.id, titel: z.titel, text: z.text })), takt: nachArt("takt").map((z) => ({ id: z.id, titel: z.titel, text: z.text })), fragen: nachArt("frage").map((z) => ({ id: z.id, titel: z.titel, text: z.text })), diff --git a/workspace/anleitung.html b/workspace/anleitung.html index 2f3aa847..635e85ae 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 9430b33b..84ff6aab 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`. --> - - + + - +