Vier Meldungen -- und dahinter zweimal derselbe alte Fehler

DIE SYMBOLE IN DER TAGESKACHEL WAREN DIESELBEN -- OHNE IHRE GESTALTUNG

Sie kommen vom selben Bauer wie alle anderen, bekamen aber nie dessen
Aussehen: Alle Lagenregeln waren auf `.kachel__svg` eingegrenzt, und
dieses Zeichen heisst `.dran__svg`. Uebrig blieb eine flache Kontur --
daneben, auf derselben Seite, dieselben Zeichen mit drei Lichtern,
Tiefe und Randlicht. Die Regeln gelten jetzt fuer beide Traeger, und
das Feld darum ist dasselbe gefasste Schild wie auf den Kacheln.

"UNDEFINED" IM CHAT -- DERSELBE FEHLER WIE HEUTE FRUEH, NUR ALS OBJEKT

Ueber Cigdems Namen stand woertlich "UNDEFINED". Die Rollennamen
standen als Objekt in FUENF Skripten (chat.js zweimal, dateien.js,
kalender.js zweimal), in keinem davon 'spicy'.

Heute Frueh war es dieselbe Sache als Menge (`new Set([...])`), und
seitdem sucht `pruef-css-klassen.mjs` danach. Ein Muster, das nur eine
Schreibweise kennt, findet auch nur eine -- die Pruefung sucht jetzt
auch nach Rollen-OBJEKTEN, mit Gegenprobe. Die Namen stehen an EINER
Stelle in bereiche.js.

DOGFATHER UND SPICY MEDIA SIND IM CHAT FUER JEDEN ERREICHBAR

DogFather kam bisher als Nebeneffekt ueber die Betreuungskette mit
hinein, Spicy Media gar nicht -- die Rolle steht in keiner Kette, sie
steht daneben. Beide werden jetzt ausdruecklich hinzugefuegt: Eine
Zustaendigkeit, die nur zufaellig aus einer anderen Regel herausfaellt,
faellt beim naechsten Umbau genauso zufaellig wieder heraus.
Gemessen aus der Sicht eines Creators -- wer bei ihm ankommt, kommt
ueberall an.

DIE PERSONENLISTE: SPICY MEDIA SIEHT ALLES AUSSER DOGFATHER

Der Abschnitt "Spicy Media" fehlte in der Liste komplett -- die Rolle
gibt es seit heute Frueh, die Personenseite kannte sie nicht. Und Spicy
Media selbst sah dort bisher nur das Anlegen-Formular; sie bekommt
jetzt die Liste, ohne DogFathers Zeile, und weiterhin ohne Codes,
Sperren, Loeschen und Protokoll. Ueberblick ist nicht Verwaltung.

ZWEI FOLGEFEHLER, BEIDE VON DEN PRUEFUNGEN GEFUNDEN

  * `next("route")` TUT DAS GEGENTEIL VON DEM, WONACH ES KLINGT. Mein
    erster Versuch war eine Ausnahme-Route VOR der Schranke, die mit
    `next("route")` weiterreicht -- das ueberspringt aber die restlichen
    Handler DIESER Route und geht zur naechsten Schicht, also genau zur
    Schranke. Spicy bekam weiter 404, die Oberflaeche verstand das als
    "nicht erlaubt" und sprang zur Startseite. Gemessen: Auf
    personen.html standen die Kategorien der STARTSEITE.
  * DAS PROTOKOLL WARF SIE VON DER SEITE. Es bleibt bei DogFather und
    antwortet ihr mit 404 -- und `hole()` versteht ein 404 unter
    `/verwaltung/` als "nicht erlaubt". Die Seite baute sich auf und
    sprang im naechsten Atemzug weg. Eine Abfrage, von der man weiss,
    dass sie 404 gibt, stellt man nicht.

UND EINE MEINER EIGENEN NEUEN PRUEFUNGEN WAR WERTLOS

"bei ihr fehlt der Abschnitt DogFather" -- gruen, mit dem Zusatz
"(keine Abschnitte)". Sie war gruen, weil die Liste bei ihr GAR NICHT
DA war. Genau der Haken, der nichts beweist. Er steht jetzt neben einem
Ergebnis, das etwas enthaelt: "Spicy Media | Manager | ...".

18 Pruefungen gelaufen, alle gruen. pruef-spicy von 49 auf 57.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-07 16:45:50 +02:00
co-authored by Claude Opus 5
parent 0ea8b27aca
commit dd1f561f3b
30 changed files with 514 additions and 238 deletions
+26 -1
View File
@@ -519,9 +519,34 @@ console.log("\n=== Rollenlisten: eine Quelle, nicht neun");
eigenbau.push(`${n}: [${rollen.join(", ")}]`);
}
}
/* UND DIESELBE SUCHE FUER ROLLEN-OBJEKTE (07.09.2026).
Die Liste oben findet Felder und Mengen. Die Namen der Rollen
stehen aber als OBJEKT da (`{ admin: 'DogFather', ... }`), und
genau in dieser Form ist der Fehler ein zweites Mal aufgetreten:
fuenf Skripte, keines mit 'spicy'. Im Chat stand ueber Cigdems
Namen woertlich "UNDEFINED".
Ein Muster, das nur eine Schreibweise kennt, findet auch nur eine.
Deshalb hier die zweite. */
for (const n of dateien) {
if (n === "bereiche.js" || n === "gate.js") continue; // Quelle bzw. Anmeldeseite
const roh = readFileSync(join(jsOrdner, n), "utf8");
const text = roh.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
for (const m of text.matchAll(/\{[^{}]*\badmin\s*:\s*['"][^'"]+['"][^{}]*\}/g)) {
if (!/\bspicy\s*:/.test(m[0])) eigenbau.push(`${n}: ${m[0].slice(0, 44)}…`);
}
}
ok(eigenbau.length === 0, eigenbau.length
? `eigene Rollenliste gefunden: ${eigenbau.slice(0, 4).join(" · ")}`
: "kein Skript baut sich eine eigene Rollenliste");
: "kein Skript baut sich eine eigene Rollenliste oder ein eigenes Rollen-Objekt");
/* GEGENPROBE fuer die Objektform -- sie muss so eines auch finden. */
const objProbe = "const X = { admin: 'DogFather', manager: 'Manager' };";
ok([...objProbe.matchAll(/\{[^{}]*\badmin\s*:\s*['"][^'"]+['"][^{}]*\}/g)]
.some((m) => !/\bspicy\s*:/.test(m[0])),
"und ein Rollen-Objekt ohne 'spicy' wird auch wirklich gefunden");
/* GEGENPROBE: Der Sucher muss so eine Liste auch WIRKLICH finden --
sonst wäre die Zeile darüber grün, weil das Muster nie greift. */
+61 -2
View File
@@ -268,8 +268,18 @@ const ruf = async (art, weg, keks, koerper) => {
}
{
/* NICHT: die Personenverwaltung. Anlegen ja, alles andere nein. */
const { status } = await ruf("GET", "/workspace/api/verwaltung/personen", keksSpicy);
ok(status === 404, `die Personenverwaltung bleibt zu (HTTP ${status})`);
const { status, daten } = await ruf("GET", "/workspace/api/verwaltung/personen", keksSpicy);
/* AM 07.09.2026 GETEILT: Die LISTE ist fuer sie offen, alles andere
nicht. Hier stand vorher ein einziges "bleibt zu" -- das ist seit
Filipes Wunsch "spicy soll genau das sehen koennen" zu grob. Aus
einer Aussage werden drei, und die dritte ist die wichtigste:
Ueberblick ist nicht Verwaltung. */
ok(status === 200, `die Personenliste ist fuer sie offen (HTTP ${status})`);
const listeRollen = (daten?.personen || []).map((x) => x.rolle);
ok(listeRollen.length > 0 && !listeRollen.includes("admin"),
`und DogFather steht nicht darin (${listeRollen.join(", ") || "leer"})`);
const prot = await ruf("GET", "/workspace/api/verwaltung/protokoll?anzahl=5", keksSpicy);
ok(prot.status === 404, `das Protokoll bleibt zu (HTTP ${prot.status})`);
const loeschen = await ruf("DELETE", `/workspace/api/verwaltung/personen/${idLuna}`, keksSpicy);
ok(loeschen.status === 404, `loeschen geht nicht (HTTP ${loeschen.status})`);
const code = await ruf("POST", `/workspace/api/verwaltung/personen/${idLuna}/code`, keksSpicy);
@@ -443,6 +453,24 @@ const ruf = async (art, weg, keks, koerper) => {
const dash = await ruf("GET", "/workspace/api/uebersicht", keksSpicy);
ok(dash.status === 200, `ihre Uebersicht kommt trotzdem (${dash.status})`);
/* IM CHAT IST SIE FUER JEDEN ERREICHBAR (07.09.2026).
Filipe: "im chat muss die spicy rolle und die dogfather rolle fuer
jeden zugaenglich sein." Gemessen aus der Sicht eines CREATORS --
der steht am Ende jeder Kette und ist damit der Prueffall: Wer bei
ihm ankommt, kommt ueberall an.
Mit Gegenprobe: Seine Liste ist nicht einfach jeder. Waere sie das,
bewiese die Zeile darueber nichts. */
const keksLuna = await anmelden("creator", "CODE-LUNA-0001");
const partner = await ruf("GET", "/workspace/api/chat/partner", keksLuna);
const rollenDrin = (partner.daten?.personen || []).map((x) => x.rolle);
ok(rollenDrin.includes("spicy"),
`ein Creator erreicht Spicy Media im Chat (${rollenDrin.join(", ") || "niemanden"})`);
ok(rollenDrin.includes("admin"), "und DogFather ebenfalls");
ok(!rollenDrin.includes("creator"),
"einen anderen Creator dagegen nicht -- die Liste ist nicht einfach jeder");
/* Und DogFather bleibt der Einzige mit dem letzten Wort. */
const { status } = await ruf("GET", "/workspace/api/verwaltung/personen", keksDogi);
ok(status === 200, `DogFather kommt weiterhin in die Verwaltung (HTTP ${status})`);
@@ -469,7 +497,38 @@ console.log("\n=== Die Anmeldeseite ===");
ok(kacheln.includes("Personen & Zugänge"),
`sie sieht die Personen-Kachel (${kacheln.length} Kacheln)`);
ok(!kacheln.includes("Automationen"), "und die Automationen-Kachel NICHT");
/* DIE PERSONENLISTE: Spicy Media sieht alle Abschnitte AUSSER
DogFather (07.09.2026, Wunsch Filipe). Geprueft an den
Ueberschriften, die wirklich auf dem Bildschirm stehen -- nicht an
der Liste im Quelltext, die sie erzeugt. */
await seite.goto(BASIS + "/workspace/personen.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(1200);
const abschnitteSpicy = await seite.$$eval(".gruppe__name, .gruppe__kopf",
(k) => k.map((x) => x.textContent.trim()).filter(Boolean));
const alsText = abschnitteSpicy.join(" | ");
ok(!/DogFather/.test(alsText),
`bei ihr fehlt der Abschnitt DogFather (${alsText.slice(0, 70) || "keine Abschnitte"})`);
await ctx.close();
/* GEGENPROBE BEI DOGFATHER: Bei ihm steht der Abschnitt "Spicy Media"
sehr wohl -- genau das hat Filipe vermisst. Ohne diese Zeile
bewiese die Zeile darueber nur, dass die Liste leer ist. */
const ctx2 = await browser.newContext({ viewport: { width: 1440, height: 950 } });
const seite2 = await ctx2.newPage();
await seite2.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await seite2.click('.rolle[data-rolle="admin"]');
await seite2.fill("#code", "CODE-DOGI-0001");
await seite2.click("button[type=submit]");
await seite2.waitForURL("**/start.html", { timeout: 25000 });
await seite2.goto(BASIS + "/workspace/personen.html", { waitUntil: "networkidle" });
await seite2.waitForTimeout(1400);
const abschnitteDogi = (await seite2.$$eval(".gruppe__name, .gruppe__kopf",
(k) => k.map((x) => x.textContent.trim()).filter(Boolean))).join(" | ");
ok(/Spicy Media/.test(abschnitteDogi),
`bei DogFather steht der Abschnitt "Spicy Media" (${abschnitteDogi.slice(0, 80)})`);
ok(/DogFather/.test(abschnitteDogi), "und seiner ebenfalls");
await ctx2.close();
await browser.close();
}
+37 -2
View File
@@ -15,7 +15,7 @@
import express from "express";
import {
db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE,
db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy,
} from "./workspace.js";
import { sicherungJetzt } from "./workspace-sicherung.js";
@@ -48,6 +48,35 @@ const NAME_MAX = 60;
function nurAdmin(req, res, next) {
const person = sitzungLesen(req);
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
/* EINE EINZIGE AUSNAHME: die LISTE fuer Spicy Media (07.09.2026).
Filipe: "spicy soll genau das sehen koennen, aber nur nicht die
rolle dogfather." Bis hierher sah die Rolle auf der Personenseite
nur das Anlegen-Formular.
Die Ausnahme steht HIER und nicht als eigene Schicht davor. Der
erste Versuch war genau das -- eine Route vor `use(nurAdmin)`, die
mit `next("route")` weiterreicht. Das tut aber das Gegenteil von
dem, wonach es klingt: `next("route")` ueberspringt die restlichen
Handler DIESER Route und geht zur naechsten passenden Schicht --
also zu nurAdmin. Spicy Media bekam weiter 404, die Oberflaeche
verstand das als "nicht erlaubt" und schickte sie auf die
Startseite zurueck. Gemessen: Auf personen.html standen die
Kategorien der STARTSEITE.
Nur LESEN, nur diese eine Adresse, nur diese eine Rolle. Codes,
Sperren, Loeschen, Zuteilung und Protokoll bleiben bei DogFather --
Ueberblick ist nicht Verwaltung. Und DogFathers eigene Zeile faellt
unten aus dem Ergebnis (`req.ohneDogFather`). */
const nurListe = req.method === "GET"
&& req.baseUrl + req.path === "/workspace/api/verwaltung/personen";
if (istSpicy(person) && nurListe) {
req.person = person;
req.ohneDogFather = true;
return next();
}
if (!istDogFather(person)) return res.status(404).json({ fehler: "nicht_gefunden" });
req.person = person;
next();
@@ -106,6 +135,11 @@ function nurDogFatherBeiLeitung(req, res, next) {
Zeichenkette genau diese -- der Server startete danach nicht mehr.) */
personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => {
try {
/* Fuer Spicy Media ohne DogFather -- gefiltert am ERGEBNIS und
nicht in der Abfrage: Die Abfrage ist lang und wird von mehreren
Feldern gelesen; eine zusaetzliche Bedingung mittendrin waere die
Stelle, an der beim naechsten Umbau jemand danebengreift. */
const ohne = req.ohneDogFather === true;
res.json({
personen: db().prepare(`
SELECT p.id, p.name, p.rolle, p.aktiv, p.erstellt, p.letzter_login,
@@ -127,7 +161,8 @@ personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => {
FROM personen p
LEFT JOIN betreuung b ON b.creator_id = p.id
LEFT JOIN scout_zuteilung sz ON sz.scout_id = p.id
ORDER BY ${ROLLEN_SORTIERUNG.replace("rolle", "p.rolle")}, p.aktiv DESC, p.name`).all(),
ORDER BY ${ROLLEN_SORTIERUNG.replace("rolle", "p.rolle")}, p.aktiv DESC, p.name`)
.all().filter((z) => !(ohne && z.rolle === "admin")),
/* Wer ueberhaupt als zustaendig eingetragen werden kann.
Bis zum 31.08.2026 waren das nur Scouts. In der Auswahl stand
+24
View File
@@ -2273,6 +2273,30 @@ export function schreibbareIds(person) {
} catch { /* im Zweifel nur die Betreuungskette */ }
}
/* DOGFATHER UND SPICY MEDIA SIND FUER JEDEN ERREICHBAR (07.09.2026).
Filipe: "im chat muss die spicy rolle und die dogfather rolle fuer
jeden zugaenglich sein."
DogFather kam bisher ueber einladbareIds() mit hinein; Spicy Media
nicht -- die Rolle steht in keiner Betreuungskette, sie steht
daneben. Genau deshalb war sie fuer einen Creator im Chat gar nicht
vorhanden, obwohl sie fuer alle zustaendig ist.
Beide werden hier ausdruecklich hinzugefuegt statt sich auf einen
Nebeneffekt zu verlassen: Eine Zustaendigkeit, die nur zufaellig
aus einer anderen Regel herausfaellt, faellt beim naechsten Umbau
genauso zufaellig wieder heraus.
Die Gegenrichtung stimmt ohne Zutun: Beide Rollen haben
`siehtAlles` und damit ohnehin `null` = jeder. */
try {
for (const z of db().prepare(
"SELECT id FROM personen WHERE rolle IN ('admin','spicy') AND aktiv = 1").all()) {
basis.add(z.id);
}
} catch { /* im Zweifel bleibt die Betreuungskette */ }
/* Sich selbst nicht -- ein Gespräch mit sich allein ist keines. */
basis.delete(person.id);
return [...basis];