Zahlen-Seite: wieder fuer alle fuenf Rollen

Filipe, mit der Kachel im Bild: "jetzt soll jede rolle diese kachel
sehen. spicy, dogfather, manager, scout und creator. aber die creator
kriegen dan quasi nur ihre daten zu sehen und der manager scout
dogfather oder spicy koennen eintragen."

Damit ist die Sperre vom 07.09.2026 aufgehoben. Geaendert sind genau
zwei Zeilen -- die Kachel in bereiche.js und die Tuer in workspace.js.
Ausdruecklich als Liste der fuenf Rollen, NICHT als `null`: `null`
hiesse auch jede Rolle, die es noch nicht gibt.

WER WAS DARF, musste ich nicht bauen -- es stand schon da und konnte
nur niemand benutzen: sichtbareCreatorIds() gibt einem Creator genau
seine eigene Nummer, darfEintragen() ist Leitung plus Scout. Das ist
exakt Filipes Satz, ohne eine einzige Aenderung am Server.

WAS DER UMBAU AUFGEDECKT HAT: "Aus Datei einlesen" hatte als einziger
Knopf NIE eine Rechteregel -- folgenlos, solange nur DogFather
hereinkam, ab heute haette ein Creator ihn gesehen und eine Absage
bekommen. Alle drei Eintrag-Knoepfe stehen jetzt in EINER Liste mit
ihrer Regel daneben, und der Knopf startet `hidden`, damit er nicht
kurz aufblitzt.

Gefunden hat das nicht das Nachdenken, sondern die Gegenprobe von
heute Mittag: Sie wurde rot, weil der Test-Scout auf der Startseite
landete -- waehrend ich nebenan eine Erklaerung "fuer Scouts und
Manager" schrieb fuer eine Seite, die beide gar nicht oeffnen konnten.

pruef-backstage-import: 36 -> 50 Pruefungen. Gemessen wird der
UNTERSCHIED zwischen den Rollen, nicht die Anwesenheit von irgendetwas:
jeder Knopf einzeln, bei Scout und Creator, dazu die Kachel auf der
Startseite und die Gegenprobe, dass die Zahlen des Creators trotzdem
dastehen. Sonst waere "der Creator sieht keinen Knopf" auch dann gruen,
wenn seine Seite leer bliebe.

Mit SCHIRM=1 legt die Pruefung zwei Bilder ab, eins je Rolle.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-11 14:05:03 +02:00
co-authored by Claude Opus 5
parent f171d0cffd
commit 8d3a79ed2a
28 changed files with 521 additions and 320 deletions
+148 -4
View File
@@ -72,7 +72,7 @@ await starten();
kind.kill();
await new Promise((r) => setTimeout(r, 600));
let idNova, idKiro, idLumi;
let idNova, idKiro, idLumi, idSam;
{
const d = new DatabaseSync(DB);
const anlegen = (name, rolle, code, tiktok) => {
@@ -84,13 +84,20 @@ let idNova, idKiro, idLumi;
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
};
anlegen("Filipe", "admin", "CODE-DOGI-0001");
anlegen("Sam", "scout", "CODE-SAM-00001");
idSam = anlegen("Sam", "scout", "CODE-SAM-00001");
/* Nova hat ein Handle, Kiro NICHT -- er wird ueber den Namen
gefunden. Lumi hat ein Handle mit anderer Schreibweise als in der
Tabelle: Genau daran zeigt sich, ob normalisiert wird. */
idNova = anlegen("Nova", "creator", "CODE-NOVA-0001", "nova_live");
idKiro = anlegen("Kiro", "creator", "CODE-KIRO-0001", null);
idLumi = anlegen("Lumi", "creator", "CODE-LUMI-0001", "LumiStreamt");
/* Sam betreut Nova. Ohne diese Zeile haette der Scout gar keinen
Creator -- die Seite waere leer, und eine Pruefung auf "der Scout
sieht den Backstage-Satz NICHT" waere gruen, weil er ueberhaupt
nichts sieht. Das ist der Unterschied zwischen einem Beweis und
einem Zufall. */
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)")
.run(idNova, idSam, jetzt);
d.close();
}
await starten();
@@ -268,6 +275,17 @@ console.log("\n=== Gegenproben ===");
ok(status === 200, `heute als Tag geht dagegen durch (HTTP ${status})`);
}
/* Vorbedingung fuer die Gegenprobe weiter unten: Sam MUSS Nova sehen.
Sonst waere "der Scout sieht den Backstage-Satz nicht" nur die
Auskunft, dass seine Seite leer ist. */
{
const a = await fetch(BASIS + "/workspace/api/leistung", { headers: { Cookie: keksScout } });
const d = await a.json().catch(() => ({}));
const namen = (d.creator || []).map((c) => c.name).join(", ");
ok(/Nova/.test(namen),
`der Scout sieht seinen Creator in der Liste (${namen || "leer"})`);
}
/* =======================================================================
7. Und die Seite selbst
======================================================================= */
@@ -320,8 +338,14 @@ console.log("\n=== Die Seite ===");
await seite.click("#netz-zu");
await seite.waitForTimeout(300);
const hinweis = await seite.$eval("#quellen-hinweis", (e) => (e.hidden ? "" : e.textContent));
ok(/Backstage/.test(hinweis) && /TikTok-Datei/.test(hinweis),
/* Gemessen wird SATZ FUER SATZ, nicht der Block. Ein Block, der
als Ganzes erscheint, erklaert einem Scout auch den Knopf, den er
nicht hat -- das ist keine Hilfe, sondern eine Absage. */
const satz = (id) => seite.$eval(id, (e) => (e.hidden ? "" : e.textContent))
.catch(() => "");
const netzSatz = await satz("#quelle-netz");
const ttSatz = await satz("#quelle-tt");
ok(/Backstage/.test(netzSatz) && /TikTok-Datei/.test(ttSatz),
"unter den Knöpfen steht dauerhaft, was die beiden Quellen sind");
/* Und der fertige Text für den Creator -- mit den zwei Angaben, an
@@ -335,6 +359,126 @@ console.log("\n=== Die Seite ===");
"und den Weg in der App");
ok(/nicht gespeichert/.test(fuerCreator),
"und was mit der Datei passiert -- das gehört dem Creator gesagt");
/* ---- DIESELBE SEITE, ANDERE ROLLE (11.09.2026) -----------------
Filipe, mit der Kachel im Bild: "jetzt soll jede rolle diese
kachel sehen. spicy, dogfather, manager, scout und creator. aber
die creator kriegen dan quasi nur ihre daten zu sehen und der
manager scout dogfather oder spicy koennen eintragen."
Bis heute war die Seite auf ["admin"] gesperrt. Dass ich das
ueberhaupt gemerkt habe, verdanke ich dieser Gegenprobe: Sie
wurde rot, weil Sam auf der Startseite landete -- waehrend ich
nebenan eine Erklaerung "fuer Scouts und Manager" schrieb.
GEPRUEFT WIRD DER UNTERSCHIED, NICHT DIE ANWESENHEIT. Ein Scout
und ein Creator sehen dieselbe Seite; was sie unterscheidet, sind
die Knoepfe. Deshalb wird bei beiden JEDER Knopf einzeln gemessen
-- ein "sieht etwas" waere bei beiden gruen und wertlos. */
const rollenBild = async (rolle, code) => {
const c = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
const p = await c.newPage();
const fehler = [];
p.on("pageerror", (e) => fehler.push(String(e).slice(0, 140)));
await p.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await p.click(`.rolle[data-rolle="${rolle}"]`);
await p.fill("#code", code);
await p.click("button[type=submit]");
await p.waitForURL("**/start.html", { timeout: 25000 });
/* Erst die Kachel: Ein Weg, den man nur ueber die Adresse findet,
ist kein Weg. Filipe hat ausdruecklich die KACHEL gezeigt.
ABWARTEN, BIS SIE DA SIND. Der erste Entwurf las sofort nach
waitForURL -- da hatte das Skript die Kacheln noch gar nicht
gebaut, und die Pruefung meldete "keine Kachel" fuer beide
Rollen. Das war kein Befund, das war eine zu frueh gestellte
Frage. Und weil KEINE Kachel gefunden wurde, haette ein
schlichtes "mindestens eine" den Fehler auch nicht bemerkt. */
await p.waitForSelector(".kachel__link", { timeout: 20000 });
const kachel = await p.evaluate(() =>
[...document.querySelectorAll("a[href]")]
.some((a) => (a.getAttribute("href") || "").includes("leistung.html")));
await p.goto(BASIS + "/workspace/leistung.html", { waitUntil: "networkidle" });
await p.waitForTimeout(1800);
const bild = await p.evaluate(() => {
const h = (id) => {
const e = document.getElementById(id);
return e ? e.hidden : "fehlt";
};
return {
weg: location.pathname,
kacheln: 0,
import: h("import-oeffnen"), netz: h("netz-oeffnen"), tt: h("tt-oeffnen"),
ziele: h("ziele-aendern"),
block: h("quellen-hinweis"), sNetz: h("quelle-netz"), sTt: h("quelle-tt"),
/* Wie viele Creator stehen zur Auswahl? Ein Creator darf hier
kein Auswahlfeld haben -- es haette nur ihn selbst drin. */
wahl: document.getElementById("creator-wahl")?.hidden === false
? document.querySelectorAll("#creator option").length : 0,
aendern: document.querySelectorAll(".tagzeile__knopf").length,
};
});
bild.kachel = kachel;
bild.fehler = fehler.join(" | ");
/* Bilder nur auf Zuruf (SCHIRM=1). Eine Pruefung, die bei jedem
Lauf Dateien ablegt, muellt das Verzeichnis zu -- und ein Bild
beweist ohnehin nichts, das misst der Code darueber. Es ist zum
HINSEHEN da, wenn Filipe fragt, wie es fuer eine Rolle aussieht. */
if (process.env.SCHIRM) {
await p.screenshot({ path: join(HIER, `pruef-zahlen-${rolle}.png`), fullPage: false });
}
await c.close();
return bild;
};
const scoutBild = await rollenBild("scout", "CODE-SAM-00001");
ok(scoutBild.kachel === true && /leistung\.html$/.test(scoutBild.weg),
`der Scout findet die Kachel und kommt auf die Seite (${scoutBild.weg})`);
ok(scoutBild.fehler === "", `kein Skriptfehler beim Scout (${scoutBild.fehler || "keiner"})`);
ok(scoutBild.tt === false && scoutBild.import === false && scoutBild.ziele === false,
"der Scout darf eintragen: Datei, TikTok-Datei und Ziele sind da");
ok(scoutBild.netz === true,
"die Backstage-Tabelle bleibt ihm zu -- sie gilt für ALLE Creator");
ok(scoutBild.sTt === false && scoutBild.sNetz === true,
"und er liest nur die Erklärung zu dem Weg, den er auch hat");
ok(scoutBild.aendern > 0,
`er kann einen Tag eintragen (${scoutBild.aendern} Knöpfe in der Liste)`);
const creatorBild = await rollenBild("creator", "CODE-NOVA-0001");
ok(creatorBild.kachel === true && /leistung\.html$/.test(creatorBild.weg),
`der Creator findet die Kachel und kommt auf die Seite (${creatorBild.weg})`);
ok(creatorBild.fehler === "", `kein Skriptfehler beim Creator (${creatorBild.fehler || "keiner"})`);
ok(creatorBild.import === true && creatorBild.netz === true
&& creatorBild.tt === true && creatorBild.ziele === true,
"der Creator sieht KEINEN einzigen Eintrag-Knopf");
ok(creatorBild.aendern === 0,
`auch nicht an den einzelnen Tagen (${creatorBild.aendern} Knöpfe)`);
ok(creatorBild.block === true,
"und keinen Quellen-Hinweis -- eine Erklärung ohne Knopf ist Rauschen");
ok(creatorBild.wahl === 0,
`und kein Auswahlfeld für fremde Creator (${creatorBild.wahl} Einträge)`);
/* GEGENPROBE ZUR GEGENPROBE: Dass der Creator nichts sieht, darf
nicht daran liegen, dass die Seite bei ihm leer bleibt. Seine
eigenen Zahlen muessen da sein -- sonst waere alles oben gruen,
weil nichts geladen hat. */
const creatorZahlen = await (async () => {
const c = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
const p = await c.newPage();
await p.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await p.click('.rolle[data-rolle="creator"]');
await p.fill("#code", "CODE-NOVA-0001");
await p.click("button[type=submit]");
await p.waitForURL("**/start.html", { timeout: 25000 });
await p.goto(BASIS + "/workspace/leistung.html", { waitUntil: "networkidle" });
await p.waitForTimeout(1800);
const t = await p.evaluate(() => document.getElementById("tagetabelle")?.textContent || "");
await c.close();
return t;
})();
ok(/\d/.test(creatorZahlen) && creatorZahlen.length > 20,
`seine eigenen Zahlen stehen trotzdem da (${creatorZahlen.length} Zeichen)`);
await browser.close();
}