Files
dogfather-universe/server/pruef-entwicklung.mjs
T
DogFatherGit bc9a902ffb Die Knoepfe stehen nicht mehr vor dem Text -- und verteilt wird bei "Eure Aufgaben"
ZWEI WUENSCHE, EINE URSACHE: beide gehen auf den Block "Noch jemanden
dazunehmen" zurueck, der am 21.09. ins Aufgabenformular kam.

1. "DIE ZWEI BUTTONS SOLLEN NICHT VOR DEM TEXT STEHEN"

Gemessen bei 1280 px: "Anlegen" und "Abbrechen" lagen ueber zwei
Hinweisen, mit 363x27 und 363x10 Pixeln Ueberlappung.

Die Ursache ist eine Regel vom 17.09., und sie ist richtig: Der
Hinweis haengt ABSOLUT unter seinem Feld, damit die Eingaben auf einer
Linie bleiben. Was aus dem Fluss genommen wird, belegt aber keinen
Platz -- solange darunter nur der Rasterabstand kam, fiel das nicht
auf. Mit dem neuen Knopf wurde die Zelle hoeher, und der Hinweis
wanderte mit, direkt auf die Knoepfe.

Jetzt bekommt das Raster unter sich 44 px, wenn es einen haengenden
Hinweis gibt (`:has()`, nicht pauschal -- ein Formular ohne Hinweis
bekaeme sonst Leere geschenkt). Die Hoehe ist gemessen: ein Hinweis
ist zweizeilig 35 px hoch plus 4 px Abstand.

UND EIN ZWEITER FUND AN DERSELBEN STELLE: Der Block sass IN der Zelle
"Wer macht es?". Dadurch stand deren Eingabe bei 780..824, die
Nachbarin "Fuer welchen Kanal?" bei 833..877 -- 53 Pixel Versatz, und
genau das sieht man als "verzogen". Er steht jetzt als eigene
Rasterzeile hinter beiden; danach sind sie wieder buendig. Als eigene
Zeile ist er ausserdem ehrlicher: "Noch jemanden dazunehmen" ist ein
zweiter Schritt, kein Teil des ersten.

Gefunden hat beides pruef-formulare, die seit dem 07.09. auf buendige
Unterkanten prueft und seit dem 21.09. rot war.

2. "BEI EURE AUFGABEN DIE AUFGABEN VERTEILEN"

Filipe, zum wiederholten Mal -- und so stand es auch im Auftrag vom
21.09. (Abschnitt 2). Auf "Eure Aufgaben" steht jetzt ein Band
"Aufgabe verteilen". Es nimmt die gerade gewaehlte Person mit:
"Aufgabe fuer Kessi" fuehrt auf das Aufgabenbrett, oeffnet das
Formular und traegt sie ein.

EIN WEG, KEIN ZWEITES FORMULAR. Zwei Formulare fuer dieselbe Sache
waeren zwei Gelegenheiten, eines zu vergessen -- und man wuesste nie,
welches das richtige ist.

Beim Bauen gemessen: Das Band blieb unsichtbar, bis man eine Person
anklickte -- `window.__ich` kommt ueber das Netz und steht beim ersten
Aufruf noch nicht. Jetzt wird darauf gewartet, laengstens drei
Sekunden, statt eine Zahl aus dem Kopf zu setzen.

Zwei Pruefungen waren selbst kaputt: pruef-formulare zaehlte ein
Eingabefeld mit, das in einem zugeklappten Block liegt und Hoehe 0 hat
-- ein Feld, das man nicht sieht, kann nicht schief stehen.
pruef-entwicklung verlangte, dass ein Modi "Eure Aufgaben" NICHT
bekommt; das hat Filipe heute umgedreht.

Geprueft: pruef-formulare 19/0 (war 18 ok / 1 FEHL), pruef-entwicklung
48/0 (war 45/1), pruef-aufgabenbrett 49/0, pruef-css-klassen 30/0.
Der ganze Weg am Bildschirm gemessen: Band sichtbar, Person mitgenommen,
Formular offen, richtige Person gewaehlt, keine Skriptfehler.
2026-09-22 01:57:08 +02:00

392 lines
20 KiB
JavaScript

/* ENTWICKLUNG UND TALENTE (11.09.2026)
Filipe: "ich will dass ich genau so eine kategorie habe in der
dogfather und rechten hand rollen, wo wir aufgaben oder bewertungen
über modis eingeben können. auch für zuschauer die vielleicht modis
werden könnten wäre auch geil."
---------------------------------------------------------------------
DIE PRÜFUNG, DERENTWEGEN ES DIESE DATEI GIBT
In „Entwicklung" steht, was über Menschen aufgeschrieben wird. Wenn
diese beiden Bereiche irgendwo durchsickern, ist der Schaden nicht
technisch, sondern menschlich -- und er lässt sich nicht zurücknehmen.
Zwei Schlösser sichern das: der ZUGANG (fuehrtTeamDogi) und die
SICHTBARKEIT der Zeilen. Geprüft werden beide, und zwar von der
falschen Seite aus: Ein Modi klopft an die Liste, an eine einzelne
Zeile, ans Ändern und ans Löschen -- und muss überall dieselbe
Antwort bekommen wie jemand, für den es diese Bereiche nicht gibt.
Dazu die Brücke: Ein Eintrag lässt sich EINMAL an die Person
schicken. Gemessen wird, dass die Nachricht wirklich in ihrem Chat
ankommt (sonst wäre „geschickt" nur ein Datum) und dass ein zweiter
Versuch abgelehnt wird.
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { request } from "node:http";
const ordner = mkdtempSync(join(tmpdir(), "ws-entw-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
const { eigenerPort } = await import("./helfer-port.mjs");
const PORT = await eigenerPort(import.meta, "die Entwicklungspruefung");
process.env.PORT = `${PORT}`;
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
await new Promise((r) => setTimeout(r, 1000));
for (const art of ["uncaughtException", "unhandledRejection"]) {
process.on(art, (f) => { console.error(""); console.error("ABBRUCH:", f?.stack || f); process.exit(1); });
}
setTimeout(() => { console.error(""); console.error("ABBRUCH: 150 s vorbei."); process.exit(1); },
150000).unref?.();
let fehler = 0, geprueft = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const CREW = "crew.dogfather-universe.com";
const WS = "workspace.dogfather-universe.com";
function hole(pfad, { host = WS, methode = "GET", rumpf = null, keks = "" } = {}) {
return new Promise((fertig, schief) => {
const kopf = { Host: host };
if (rumpf) { kopf["Content-Type"] = "application/json"; kopf["Content-Length"] = Buffer.byteLength(rumpf); }
if (keks) kopf.Cookie = keks;
const a = request({ host: "127.0.0.1", port: PORT, path: pfad, method: methode, headers: kopf },
(antwort) => {
let text = "";
antwort.setEncoding("utf8");
antwort.on("data", (t) => { text += t; });
antwort.on("end", () => {
let daten = null;
try { daten = JSON.parse(text); } catch { /* kein JSON */ }
fertig({ status: antwort.statusCode, text, daten,
keks: (antwort.headers["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ") });
});
});
a.on("error", schief);
a.setTimeout(15000, () => { a.destroy(new Error("Zeitueberschreitung")); });
if (rumpf) a.write(rumpf);
a.end();
});
}
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
await hole("/workspace/api/anmelden", { methode: "POST",
rumpf: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
for (let i = 0; i < 60; i++) {
try {
const p = new DatabaseSync(process.env.WORKSPACE_DB);
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
} catch { await new Promise((r) => setTimeout(r, 250)); }
}
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768, jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
anlegen("VanVan", "hand", "CODE-HAND-0001");
const idModi = anlegen("Ayla", "modi", "CODE-TEAM-0001");
anlegen("Cigdem", "manager", "CODE-MANA-0001");
async function anmelden(rolle, code, wand) {
const a = await hole("/workspace/api/anmelden", { host: wand, methode: "POST",
rumpf: JSON.stringify({ rolle, code }) });
return { ok: a.status === 200, keks: a.keks };
}
const dogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
const hand = await anmelden("hand", "CODE-HAND-0001", CREW);
const ayla = await anmelden("modi", "CODE-TEAM-0001", CREW);
const mana = await anmelden("manager", "CODE-MANA-0001", WS);
ok(dogi.ok && hand.ok && ayla.ok && mana.ok, "alle vier sind angemeldet");
const ENTW = "/workspace/api/bereich/entwicklung";
const TAL = "/workspace/api/bereich/talente";
/* =======================================================================
1. Es gibt die beiden Bereiche -- und ihre Felder
======================================================================= */
console.log("");
console.log("=== Die beiden Bereiche ===");
{
const a = await hole(ENTW, { host: CREW, keks: dogi.keks });
ok(a.daten?.einstellung?.name === "Entwicklung",
`DogFather kann Entwicklung oeffnen (${a.daten?.einstellung?.name})`);
const arten = Object.keys(a.daten?.einstellung?.arten || {});
ok(arten.length === 5, `fuenf Arten (${arten.join(", ")})`);
/* DIE REIHENFOLGE IST ABSICHT: Wer ein Formular oeffnet, dessen
erstes Feld "Problem" heisst, schreibt Probleme auf. */
ok(arten[0] === "staerke" && arten[1] === "dank",
"und die ersten beiden sind die guten");
ok(arten.includes("last"),
"„Zu viel gerade“ ist dabei -- der haeufigste Grund aufzuhoeren");
/* DAS NOTIZBRETT MUSS ERREICHBAR BLEIBEN -- gerade WEIL die Kachel
nicht mehr darauf zeigt.
Genau hier ist am 11.09.2026 etwas kaputtgegangen: Die Liste der
Bretter, die ein Modi oder die rechte Hand oeffnen darf, wird aus
den KACHELZIELEN abgeleitet. Als die Kacheln auf die Katalogseiten
umgestellt wurden, fielen beide Bretter aus der Liste -- und die
Seite verlinkte auf ein 404. Kein Fehler im Riegel, sondern in
seiner Quelle.
Diese Zeile ist die einzige, die das gemerkt hat. */
const b = await hole(TAL, { host: CREW, keks: hand.keks });
ok(b.daten?.einstellung?.name === "Talente",
`die rechte Hand kann Talente oeffnen (${b.daten?.einstellung?.name})`);
const bE = await hole(ENTW, { host: CREW, keks: hand.keks });
ok(bE.daten?.einstellung?.name === "Entwicklung",
`und das Notizbrett Entwicklung auch (${bE.daten?.einstellung?.name})`);
const tArten = Object.keys(b.daten?.einstellung?.arten || {});
ok(tArten.includes("empfohlen"),
`die Empfehlung aus dem Team ist dabei (${tArten.join(", ")})`);
}
/* =======================================================================
2. DIE EIGENTLICHE PRUEFUNG: Wer NICHT hineinkommt
======================================================================= */
console.log("");
console.log("=== Wer nicht hineinkommt ===");
let eintragId = 0;
{
/* Erst ein Eintrag, damit es ueberhaupt etwas zu verbergen gibt --
sonst waere jede Sperre unten gruen ueber einer leeren Liste. */
const a = await hole(ENTW, { host: CREW, methode: "POST", keks: dogi.keks,
rumpf: JSON.stringify({
art: "staerke", titel: "Ruhig geblieben, als es kippte",
text: "Hat den Streit im Chat gedreht, ohne jemanden bloßzustellen.",
datum: "2026-09-11", creator_id: idModi, einsatz: "Beim nächsten Live wieder ihr überlassen",
}) });
eintragId = a.daten?.id;
ok(a.status === 201 && eintragId > 0,
`ein Eintrag ueber Ayla (${a.status}) ${a.status === 201 ? "" : a.text.slice(0, 160)}`);
for (const [wer, keks, wand] of [["ein Teammitglied", ayla.keks, CREW],
["ein Manager", mana.keks, WS]]) {
const liste = await hole(ENTW, { host: wand, keks });
ok(liste.status === 404, `${wer}: die Liste gibt es nicht (${liste.status})`);
const aendern = await hole(`${ENTW}/${eintragId}`, { host: wand, methode: "PATCH",
keks, rumpf: JSON.stringify({ titel: "umgeschrieben" }) });
ok(aendern.status === 404, `${wer}: aendern geht nicht (${aendern.status})`);
const weg = await hole(`${ENTW}/${eintragId}`, { host: wand, methode: "DELETE", keks });
ok(weg.status === 404, `${wer}: loeschen auch nicht (${weg.status})`);
const tal = await hole(TAL, { host: wand, keks });
ok(tal.status === 404, `${wer}: Talente ebenfalls nicht (${tal.status})`);
}
/* Und die Zeile steht unveraendert da -- sonst waeren die 404 oben
Antworten auf Anfragen, die trotzdem gewirkt haben. */
const heil = d.prepare("SELECT titel FROM eintraege WHERE id = ?").get(eintragId);
ok(heil?.titel === "Ruhig geblieben, als es kippte", "der Eintrag ist unveraendert");
/* GEGENPROBE: Die beiden, die duerfen, sehen ihn sehr wohl. Ohne sie
hiesse "niemand sieht ihn" auch dann gruen, wenn er gar nicht
angekommen waere. */
for (const [wer, keks] of [["DogFather", dogi.keks], ["die rechte Hand", hand.keks]]) {
const a = await hole(ENTW, { host: CREW, keks });
ok((a.daten?.eintraege || []).some((e) => e.id === eintragId),
`${wer} sieht ihn`);
}
}
/* =======================================================================
3. Die Bruecke: einmal an die Person schicken
======================================================================= */
console.log("");
console.log("=== An die Person schicken ===");
{
const a = await hole(`${ENTW}/${eintragId}/senden`, { host: CREW, methode: "POST",
keks: dogi.keks, rumpf: "{}" });
ok(a.status === 200, `geschickt (${a.status})`);
/* KOMMT SIE WIRKLICH AN? Ohne diese Zeile waere "geschickt" nur ein
Datum in einer Spalte. Nachgesehen wird in AYLAS Chat, nicht in
der Antwort der Route. */
const raeume = await hole("/workspace/api/chat/raeume", { host: CREW, keks: ayla.keks });
const raum = (raeume.daten?.raeume || [])[0];
ok(!!raum, `Ayla hat ein Gespraech (${(raeume.daten?.raeume || []).length})`);
const verlauf = await hole(`/workspace/api/chat/raeume/${raum?.id}/nachrichten`,
{ host: CREW, keks: ayla.keks });
const texte = (verlauf.daten?.nachrichten || []).map((n) => n.text || "").join(" ");
ok(/Ruhig geblieben, als es kippte/.test(texte), "und die Nachricht steht darin");
ok(/Das läuft gut/.test(texte),
"mit der Art davor -- sonst waere es ein Fetzen ohne Zusammenhang");
ok(/Beim nächsten Live wieder ihr überlassen/.test(texte),
"und dem, was daraus folgen soll");
/* EINMAL IST EINMAL. Zweimal dasselbe Lob ist kein doppeltes Lob. */
const b = await hole(`${ENTW}/${eintragId}/senden`, { host: CREW, methode: "POST",
keks: dogi.keks, rumpf: "{}" });
ok(b.status === 409, `ein zweites Mal wird abgelehnt (${b.status})`);
const nachher = await hole(`/workspace/api/chat/raeume/${raum?.id}/nachrichten`,
{ host: CREW, keks: ayla.keks });
ok((nachher.daten?.nachrichten || []).length === (verlauf.daten?.nachrichten || []).length,
"und es kam keine zweite Nachricht an");
/* Ein Eintrag ohne Person laesst sich nicht schicken -- an wen auch. */
const ohne = await hole(ENTW, { host: CREW, methode: "POST", keks: dogi.keks,
rumpf: JSON.stringify({ art: "wachsen", titel: "Allgemeine Notiz", datum: "2026-09-11" }) });
const c = await hole(`${ENTW}/${ohne.daten?.id}/senden`, { host: CREW, methode: "POST",
keks: dogi.keks, rumpf: "{}" });
ok(c.status === 400, `ein Eintrag ohne Person geht nicht raus (${c.status})`);
}
/* =======================================================================
4. Die Kacheln
======================================================================= */
console.log("");
console.log("=== Die Kacheln ===");
{
/* DIE KACHELN ZEIGEN SEIT DEM 11.09.2026 AUF DIE KATALOGSEITEN.
Vorher stand hier `b=(entwicklung|talente)` -- das Ziel war ein
Brett. Filipe: "ich will dass fertige aufgaben da stehen und so".
Seither fuehrt die Kachel auf entwicklung.html bzw. talente.html;
das Notizbrett gibt es weiter und ist von dort verlinkt.
GEPRUEFT WIRD DER NAME DER SEITE UND NICHT MEHR DAS BRETT -- eine
Kachel, die auf ein Brett zeigt, waere ab jetzt der Rueckschritt,
den diese Zeile melden soll. */
for (const [wer, keks] of [["DogFather", dogi.keks], ["die rechte Hand", hand.keks]]) {
const a = await hole("/workspace/api/ich", { host: CREW, keks });
const alle = [...(a.daten?.bereiche || []), ...(a.daten?.bereiche_zusatz || [])];
/* NACH DEM NAMEN UND NICHT NACH DEM ZIEL.
Seit dem 11.09.2026 zeigen DREI Kacheln auf entwicklung.html: die
beiden Katalogkacheln und „Wie geht's dir?" -- dieselbe Seite,
drei verschiedene Zwecke, weil der Server entscheidet, was ein
Modi dort sieht. Ein Filter nach dem Ziel zaehlt sie alle und
meldet einen Fehler, wo keiner ist.
Der Name ist hier die ehrlichere Frage: Es geht um DIESE zwei
Kacheln, nicht um alles, was zufaellig dieselbe Adresse hat. */
/* UMBENANNT AM 19.09.2026: aus "Entwicklung" wurde "Eure Aufgaben"
(die Kachel fuehrt auf den Aufgabenkatalog). Der Name steht hier
weiterhin, weil der Kommentar darueber ihn ausdruecklich als die
ehrlichere Frage bezeichnet -- nur eben der neue. */
const beide = alle.filter((k) => ["Eure Aufgaben", "Talente"].includes(k.name));
ok(beide.length === 2, `${wer}: beide Kacheln (${beide.map((k) => k.name).join(", ") || "keine"})`);
ok(beide.every((k) => k.gruppe === "Entwicklung & Nachwuchs"),
`${wer}: in einer eigenen Gruppe (${[...new Set(beide.map((k) => k.gruppe))].join(", ")})`);
}
/* DIE DRITTE KACHEL -- und sie gehoert ausdruecklich ALLEN im Team.
„Wie geht's dir?" ist die einzige Kachel des Hauses, deren Inhalt
niemand ausser der Person selbst je sieht. Sie steht auch bei
DogFather und der rechten Hand: Wenn es jemandem zu viel wird,
dann ihnen genauso. Eine Kachel, die nur die anderen ausfuellen,
waere genau die Schieflage, die dieses Haus nicht haben soll. */
for (const [wer, keks] of [["DogFather", dogi.keks], ["die rechte Hand", hand.keks],
["ein Modi", ayla.keks]]) {
const x = await hole("/workspace/api/ich", { host: CREW, keks });
const alleX = [...(x.daten?.bereiche || []), ...(x.daten?.bereiche_zusatz || [])];
/* SEIT DEM 15.09.2026 HAT SIE EINE EIGENE SEITE.
Bis dahin zeigte sie auf entwicklung.html -- und damit auf Namen
und Beobachtungen ueber ANDERE, waehrend auf der Kachel steht
"die Antworten sieht nur du". Filipe hat es gemeldet, und die
Zeile hier hat den Fehler mitgetragen, weil sie genau danach
gesucht hat. */
const befinden = alleX.find((k) => k.ziel === "befinden.html");
ok(!!befinden, `${wer} hat „${befinden?.name || "—"}"`);
ok(befinden?.gruppe === "Für dich",
`und sie steht bei „Für dich" (${befinden?.gruppe})`);
/* GEGENPROBE: Die Kachel, die VERTRAULICHKEIT VERSPRICHT, fuehrt
nicht dorthin, wo andere Menschen stehen.
HIER STAND EINE NAMENSLISTE (20.09.2026 ersetzt). Sie lautete
`!["Eure Aufgaben", "Talente"].includes(k.name)` -- und war damit
eine abgeschriebene Liste, genau die Sorte, vor der dieses Haus
an drei Stellen warnt. Als ein Modi eine eigene Kachel bekam
("Deine Aufgaben", weil "Eure" aus seiner Sicht falsch ist),
wurde diese Pruefung rot, OHNE dass irgendetwas kaputt war. Eine
rote Pruefung, die rot bleibt, gewoehnt einem das Hinsehen ab --
sie ist schaedlicher als gar keine.
Geprueft wird jetzt, was Filipe eigentlich gemeldet hat: Auf der
Kachel stand "die Antworten sieht nur du", und sie fuehrte auf
eine Seite mit Namen anderer Leute. Das ist eine Eigenschaft des
VERSPRECHENS, nicht des Namens -- und Versprechen stehen im
Untertitel. Wer morgen eine Kachel "Dein Kopf" nennt und dasselbe
verspricht, ist damit mitgeprueft; die Namensliste haette ihn
durchgelassen. */
const verspricht = (k) => /nur du|nur dich|siehst nur du/i.test(k.unter || "");
const heikel = alleX.filter((k) => verspricht(k) && k.ziel === "entwicklung.html");
ok(alleX.some(verspricht) && heikel.length === 0,
`${wer}: keine Kachel verspricht Vertraulichkeit und fuehrt doch auf die`
+ ` Entwicklungsseite (${alleX.filter(verspricht).length} mit Versprechen geprueft)`);
/* UND DIE ECHTE GRENZE, die bisher NIEMAND gemessen hat.
Ob eine Kachel richtig zeigt, ist Beschriftung. Ob die Seite
dahinter fremde Namen herausgibt, sind Daten -- und nur das
schuetzt jemanden wirklich. Ein Modi darf dort NIEMANDEN
bekommen ausser sich selbst. */
const lage = await hole("/workspace/api/entwicklung/lage", { host: CREW, keks });
const fremde = (lage.daten?.personen || []).filter((q) => q.id !== undefined);
if (wer === "ein Modi") {
ok(lage.status === 200 && fremde.length === 0,
`${wer} bekommt auf der Entwicklungsseite KEINE fremden Namen (${fremde.length})`);
} else {
/* Die Gegenprobe zur Zeile darueber: Waere der Filter kaputt und
liesse niemanden durch, saehe auch die Leitung nichts -- und
die Modi-Zeile waere trotzdem gruen. Beide Richtungen gehoeren
gemessen, sonst beweist keine etwas. */
ok(lage.status === 200 && fremde.length > 0,
`${wer} bekommt sie sehr wohl (${fremde.length}) -- der Filter ist nicht einfach zu`);
}
}
const a = await hole("/workspace/api/ich", { host: CREW, keks: ayla.keks });
const alle = [...(a.daten?.bereiche || []), ...(a.daten?.bereiche_zusatz || [])];
/* UMGEDREHT AM 22.09.2026 -- fuer "Eure Aufgaben", nicht fuer
"Talente".
---------------------------------------------------------------------
Hier stand, ein Modi bekomme BEIDE Kacheln nicht angeboten. Filipe
zum Bildschirmfoto der Gruppe "Entwicklung & Nachwuchs": "die modis
sollen diesen bereich und diese zwei kacheln auch sehen, die sollen
aber nur ihre aufgaben und daten sehen."
DASS ER SIE SIEHT, IST KEIN WIDERSPRUCH ZUR ZEILE DARUEBER: Die
misst, dass auf der Seite keine FREMDEN Namen stehen -- und das
gilt unveraendert. Die Kachel oeffnet die Seite; was darauf steht,
entscheidet der Server.
"TALENTE" BLEIBT DRAUSSEN. Dort stehen Notizen ueber Zuschauer, die
nichts davon wissen. */
ok(alle.some((k) => k.name === "Eure Aufgaben"),
`ein Teammitglied bekommt "Eure Aufgaben" angeboten (${alle.length} Kacheln)`);
ok(alle.some((k) => k.name === "Entwicklung"),
`und "Entwicklung" ebenso`);
ok(!alle.some((k) => k.name === "Talente"),
`"Talente" aber nicht -- dort stehen Notizen ueber Fremde`);
/* Kein Farbton doppelt -- zwei Kacheln in derselben Farbe sind ein
Fehler, den man sonst erst am Bildschirm findet. */
const dogiIch = await hole("/workspace/api/ich", { host: CREW, keks: dogi.keks });
const toene = [...(dogiIch.daten?.bereiche || [])].map((k) => k.ton).filter((t) => t !== undefined);
ok(new Set(toene).size === toene.length, `kein Farbton doppelt (${toene.length} Kacheln)`);
}
d.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log("");
console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"} -- ${geprueft} geprueft, ${fehler} Fehler`);
process.exit(fehler ? 1 : 0);