diff --git a/server/pruef-jeder-hat-eine-seite.mjs b/server/pruef-jeder-hat-eine-seite.mjs new file mode 100644 index 00000000..d8210221 --- /dev/null +++ b/server/pruef-jeder-hat-eine-seite.mjs @@ -0,0 +1,376 @@ +/* ===================================================================== + JEDER HAT EINE EIGENE SEITE -- UND ALLES STEHT IN DER RICHTIGEN + REIHENFOLGE (19.09.2026) + + Zwei Auftraege von Filipe, die dieselbe Frage stellen: Was sieht wer, + und in welcher Ordnung? + + "jeder soll ein steckbrief haben, jeder der ein account hat, + rechte hand, modis und community, jeder soll genau wie ich foto + und so hinzufuegen koennen." + + "ich will das oben die sachen fuer dogfather sind, dan die sachen + fuer modis und dan community bereich ... was sie nicht sehen sehen + sie nicht." + + WAS HIER SCHIEFGEHEN KANN, UND ZWAR LAUTLOS: + + 1. KACHEL OHNE TUER. Die Kachel steht da, die Seite ist zu -- ein + Knopf, der auf die Startseite zurueckwirft. Umgekehrt genauso: + Tuer offen, keine Kachel, also findet es niemand. + + 2. WER IN KEINEN ABSCHNITT PASST, VERSCHWINDET. Genau das war der + Fall: assets/js/steckbrief.js kannte drei Gruppen (Management, + Scouting, Creator). Die rechte Hand, die Modis und die Community + fielen heraus -- ohne Fehler, ohne leere Liste. Sie waren einfach + nicht da. + + 3. DIE REIHENFOLGE ENTSTEHT AUS VERSEHEN. Sie hing davon ab, welche + Kachelliste an welche angehaengt wurde. So landete die Moderation + hinter allen sieben Brettern der Community. + + HINWEIS AN MICH SELBST: In den Meldungstexten stehen hier bewusst + KEINE typografischen Anfuehrungszeichen. Ein oeffnendes Zeichen, das + versehentlich mit einem geraden geschlossen wird, beendet den String + mitten im Satz -- das ist mir beim Schreiben dieser Datei zweimal + passiert. In Kommentaren sind sie harmlos, in Strings nicht. + + Aufrufen mit: node server/pruef-jeder-hat-eine-seite.mjs + ===================================================================== */ +import { mkdtempSync, rmSync, readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { request as httpAnfrage } from "node:http"; +import { portMussFreiSein } from "./helfer-port.mjs"; + +const PORT = await portMussFreiSein(4191, "pruef-jeder-hat-eine-seite"); +const ordner = mkdtempSync(join(tmpdir(), "ws-seite-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(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 }); }; +import { notbremse } from "./helfer-notbremse.mjs"; +await import("./index.js"); +notbremse(150_000, "pruef-jeder-hat-eine-seite"); +await new Promise((r) => setTimeout(r, 700)); + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); + +let fehler = 0, geprueft = 0; +const melde = (t) => console.log(t); +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const CREW = "crew.dogfather-universe.com"; +const WURZEL = join(import.meta.dirname, ".."); + +function roh(pfad, host, kopf = {}, koerper = null) { + return new Promise((fertig, schief) => { + const a = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: pfad, + method: koerper ? "POST" : "GET", + headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf }, + }, (antwort) => { + let text = ""; + antwort.on("data", (s) => { text += s; }); + antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers })); + }); + a.on("error", schief); + if (koerper) a.write(JSON.stringify(koerper)); + a.end(); + }); +} +const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } }; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes } = await import("node:crypto"); + +/* Eine Person je Rolle, die im Treff vorkommt -- plus eine aus der + Agentur als Gegenprobe. */ +const LEUTE = [ + ["Filipe", "admin", "CODE-DOGI-0001"], + ["Rieke", "hand", "CODE-HAND-0001"], + ["Kessi", "modi", "CODE-MODI-0001"], + ["Mara", "gast", "CODE-GAST-0001"], + ["Nico", "creator", "CODE-CREA-0001"], +]; +{ + const d = new DatabaseSync(process.env.WORKSPACE_DB); + for (const [name, rolle, code] of LEUTE) { + const salz = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salz, 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, salz, 32768, new Date().toISOString()); + } + d.close(); +} + +async function anmelden(rolle, code, host) { + let a = await roh("/workspace/api/anmelden", host, {}, { rolle, code }); + if (a.code === 400 && a.text.includes("alter_offen")) { + a = await roh("/workspace/api/anmelden", host, {}, { rolle, code, alter_ok: true }); + } + return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; "); +} + +/* WELCHE ADRESSE GEHOERT ZU WELCHER ROLLE. Die Trennung der beiden + Haeuser ist eine Hausregel, keine Nebensache: Team Dogi und die + Community leben auf crew.*, die Agentur auf workspace.*. Wer das in + einer Pruefung uebersieht, misst eine Schranke und nennt sie Fehler. */ +const AGENTUR = "workspace.dogfather-universe.com"; +const HAUS_VON = { + admin: CREW, hand: CREW, modi: CREW, gast: CREW, + creator: AGENTUR, scout: AGENTUR, manager: AGENTUR, spicy: AGENTUR, +}; + +const kekse = {}; +for (const [, rolle, code] of LEUTE) kekse[rolle] = await anmelden(rolle, code, HAUS_VON[rolle]); + +/* ======================================================================= + 1. JEDER KOMMT AUF SEINE EIGENE SEITE + ======================================================================= */ +melde(""); +melde("=== 1. Die Tuer zum Steckbrief ==="); +{ + /* JEDE ROLLE AUF IHRER EIGENEN ADRESSE. + + Der erste Lauf hat hier einen echten Unterschied sichtbar gemacht: + Ein Creator bekommt auf crew.dogfather-universe.com eine 302 -- + die Hausregel trennt Agentur und Team Dogi, und das ist richtig + so. Haette ich die Pruefung nicht danach getrennt, haette ich ein + funktionierendes Schloss fuer einen Fehler gehalten. */ + for (const [name, rolle] of LEUTE) { + const haus = HAUS_VON[rolle]; + const a = await roh("/workspace/steckbrief.html", haus, { cookie: kekse[rolle] }); + ok(a.code === 200, `${name} kommt auf den Steckbrief (${a.code}, ${haus})`); + } + const b = await roh("/workspace/steckbrief.html", CREW); + ok(b.code === 302 || b.code === 401 || b.code === 403, + `ohne Anmeldung nicht (${b.code})`); +} + +melde(""); +melde("=== 2. Und bekommt dort seinen eigenen Eintrag ==="); +{ + for (const [name, rolle] of LEUTE) { + const a = json(await roh("/workspace/api/steckbrief/mein", HAUS_VON[rolle], { cookie: kekse[rolle] })); + ok(a.steckbrief?.name === name, + `${name} sieht seinen eigenen (${a.steckbrief?.name})`); + /* DAS FELD FUERS BILD MUSS DA SEIN -- sonst gibt es nichts + hochzuladen, und Filipes Satz (genau wie ich foto und so + hinzufuegen) waere nicht erfuellt. */ + ok("bild" in (a.steckbrief || {}), " und hat ein Feld fuers Bild"); + ok(Array.isArray(a.kanaele) && a.kanaele.length > 0, + ` und ${a.kanaele?.length} Kanaele zum Eintragen`); + } +} + +/* ======================================================================= + 3. NIEMAND FAELLT AUS DER UEBERSICHT + ======================================================================= */ +melde(""); +melde("=== 3. Jede Rolle hat einen Abschnitt ==="); +{ + const { ROLLEN_NAME } = await import("./workspace.js"); + const alleRollen = Object.keys(ROLLEN_NAME); + ok(alleRollen.length >= 6, `es gibt ${alleRollen.length} Rollen im Haus`); + + const a = json(await roh("/workspace/api/steckbrief", CREW, { cookie: kekse.admin })); + const liste = a.steckbriefe || []; + /* NICHT ALLE FUENF -- UND DAS IST RICHTIG SO. + + Der erste Lauf erwartete hier fuenf und bekam vier. Der Fehler lag + in der Erwartung: Auf der Crew-Adresse gehoert der Creator nicht + dazu, die beiden Haeuser sind getrennt. Haette ich die Zahl auf + `>= 1` gesetzt, waere die Pruefung gruen geworden und haette + nichts mehr gemessen -- also wird genau das geprueft, was gelten + soll: die vier aus dem Haus, und der Creator NICHT. */ + const drin = liste.map((z) => z.name).sort(); + ok(drin.length === 4 && !drin.includes("Nico"), + `auf der Crew-Adresse sieht DogFather die vier des Hauses: ${drin.join(", ")}`); + + let ohneAbschnitt = 0; + const unterWeitere = []; + for (const p of liste) { + if (!p.abschnitt) ohneAbschnitt++; + else if (p.abschnitt === "Weitere") unterWeitere.push(`${p.name} (${p.rolle})`); + } + ok(liste.length > 0 && ohneAbschnitt === 0, + `alle ${liste.length} haben einen Abschnitt (${ohneAbschnitt} ohne)`); + ok(unterWeitere.length === 0, + `keiner landet im Sammelplatz Weitere${unterWeitere.length ? ": " + unterWeitere.join(", ") : ""}`); + + /* JEDE ROLLE DES HAUSES, nicht nur die fuenf angelegten Leute. + Sonst waere dieser Abschnitt eine Aussage ueber meine Testdaten. */ + const { abschnittFuer } = await import("./workspace-steckbrief.js"); + const vergessen = alleRollen.filter((r) => abschnittFuer(r).name === "Weitere"); + ok(vergessen.length === 0, + `alle ${alleRollen.length} Rollen sind zugeordnet${vergessen.length ? " -- fehlt: " + vergessen.join(", ") : ""}`); + + /* GEGENPROBE. Ohne sie waere "niemand faellt heraus" auch dann wahr, + wenn gar nichts zugeordnet wuerde. */ + ok(abschnittFuer("gibtsnicht").name === "Weitere", + "eine unbekannte Rolle landet sichtbar im Sammelplatz, statt zu verschwinden"); + + /* UND KEINE ROLLENNAMEN DER COMMUNITY IM BROWSER. */ + const jsDatei = readFileSync(join(WURZEL, "workspace/assets/js/steckbrief.js"), "utf8"); + let gefunden = []; + for (const wort of ["'modi'", "'hand'", "'gast'", '"modi"', '"hand"', '"gast"']) { + if (jsDatei.includes(wort)) gefunden.push(wort); + } + ok(gefunden.length === 0, + `keine Rollennamen in der ausgelieferten Datei${gefunden.length ? ": " + gefunden.join(", ") : ""}`); +} + +/* ======================================================================= + 4. DIE REIHENFOLGE DER GRUPPEN + ======================================================================= */ +melde(""); +melde("=== 4. Oben die Arbeit, dann Moderation, dann Community ==="); +{ + const { bereicheFuer } = await import("./workspace.js"); + + const folgeVon = (rolle) => { + const k = bereicheFuer({ rolle }); + if (!Array.isArray(k)) return null; + const gesehen = []; + for (const x of k) if (!gesehen.includes(x.gruppe)) gesehen.push(x.gruppe); + return gesehen; + }; + + const modi = folgeVon("modi"); + ok(!!modi?.includes("Moderation"), + `ein Modi hat eine eigene Moderationsgruppe (${modi?.join(" | ")})`); + ok(!!modi?.includes("Community"), "und die Community daneben"); + /* SIE MUESSEN GETRENNT SEIN -- vorher trug die Moderationskachel + dieselbe Gruppe wie die Bretter und verschwand darin. */ + ok(modi?.indexOf("Moderation") !== modi?.indexOf("Community"), + "und beide sind wirklich zwei Gruppen, nicht eine"); + + const gast = folgeVon("gast"); + ok(!!gast?.includes("Community"), `ein Mitglied hat den Treff (${gast?.join(" | ")})`); + ok(!!gast?.includes("Für dich"), "und eine eigene Seite"); + /* Was sie nicht sehen, sehen sie nicht. */ + ok(!gast?.includes("Moderation"), "aber KEINE Moderation"); + ok(!gast?.includes("Täglich"), "und keine Arbeitsgruppe des Teams"); + + /* DIE SORTIERREGEL IM BROWSER MUSS DIESELBEN NAMEN KENNEN. Eine + Gruppe, die dort fehlt, landet bei den Arbeitsgruppen -- fuer die + Community waere das genau falsch herum. */ + const startJs = readFileSync(join(WURZEL, "workspace/assets/js/start.js"), "utf8"); + const zuletzt = /GRUPPEN_ZULETZT\s*=\s*\[([^\]]*)\]/s.exec(startJs)?.[1] || ""; + ok(zuletzt.length > 0, "die Sortierliste steht in start.js"); + for (const g of ["Moderation", "Community", "Für dich"]) { + ok(zuletzt.includes(g), `${g} steht in der Liste am Ende`); + } + /* UND DIE ARBEITSGRUPPEN STEHEN NICHT DARIN. Sie behalten ihre + gewachsene Reihenfolge -- eine Liste, die auch sie festschreibt, + ueberschreibt bestehende Entscheidungen, ohne sie gelesen zu haben. */ + for (const g of ["Täglich", "Rund ums Live", "Rund um den Creator"]) { + ok(!zuletzt.includes(g), `${g} wird NICHT ans Ende geschoben`); + } + + /* JEDE GRUPPE, DIE ES GIBT, MUSS IN EINER DER BEIDEN LISTEN STEHEN. + Sonst landet sie stillschweigend bei der Arbeit -- was fuer die + meisten richtig ist, aber nicht fuer alle. Diese Zeile findet die + naechste neue Gruppe, bevor sie an der falschen Stelle auftaucht. */ + const alleGruppen = new Set(); + for (const rolle of ["admin", "hand", "modi", "gast"]) { + for (const g of folgeVon(rolle) || []) alleGruppen.add(g); + } + ok(alleGruppen.size >= 5, + `${alleGruppen.size} Gruppen im Haus: ${[...alleGruppen].join(", ")}`); +} + +/* ======================================================================= + 5. IM BROWSER -- steht die Kachel da, und in welcher Reihenfolge? + ======================================================================= */ +melde(""); +melde("=== 5. Im Browser ==="); +{ + let pw = null; + try { + pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); + } catch { /* dritter Ausgang */ } + + if (!pw) { + melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen."); + melde(" Das ist kein Befund. Die Abschnitte 1-4 gelten weiter."); + } else { + const browser = await pw.chromium.launch(); + try { + for (const [name, rolle] of [["Mara", "gast"], ["Kessi", "modi"], ["Filipe", "admin"]]) { + const ctx = await browser.newContext({ viewport: { width: 1400, height: 1000 } }); + await ctx.addCookies(kekse[rolle].split("; ").filter(Boolean).map((z) => { + const [n, ...r] = z.split("="); + return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" }; + })); + const seite = await ctx.newPage(); + const kaputt = []; + seite.on("pageerror", (e) => kaputt.push(String(e).slice(0, 140))); + + await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" }); + await seite.waitForTimeout(900); + + const gruppen = await seite.evaluate(() => + [...document.querySelectorAll(".gruppe")].map((g) => + (g.querySelector("h2, .gruppe__name")?.textContent || "").trim()).filter(Boolean)); + + melde(` --- ${name} sieht: ${gruppen.join(" | ")}`); + ok(gruppen.length > 0, `${name}: es werden Gruppen angezeigt (${gruppen.length})`); + + const hatSteckbrief = await seite.evaluate(() => + !!document.querySelector('a[href*="steckbrief.html"]')); + ok(hatSteckbrief, `${name}: die Steckbrief-Kachel ist da`); + + const platz = (n) => gruppen.findIndex((g) => g.includes(n)); + const pFuer = platz("Für dich"), pCom = platz("Community"), pMod = platz("Moderation"); + if (pFuer >= 0 && pCom >= 0) { + ok(pFuer > pCom, ` das Eigene steht hinter der Community (${pFuer} > ${pCom})`); + } + if (pMod >= 0 && pCom >= 0) { + ok(pMod < pCom, ` die Moderation steht VOR der Community (${pMod} < ${pCom})`); + } + if (pMod >= 0) { + const pArbeit = gruppen.findIndex((g) => /Täglich|Rund ums Live|Rund um das Team/.test(g)); + if (pArbeit >= 0) { + ok(pArbeit < pMod, ` die eigene Arbeit steht ganz oben (${pArbeit} < ${pMod})`); + } + } + if (rolle === "gast") { + ok(pMod === -1, " ein Mitglied sieht KEINE Moderationsgruppe"); + } + + await seite.goto(`http://127.0.0.1:${PORT}/workspace/steckbrief.html`, + { waitUntil: "networkidle" }); + await seite.waitForTimeout(900); + const eigen = await seite.evaluate(() => ({ + name: document.getElementById("sb-name")?.textContent?.trim() || "", + hatDatei: !!document.getElementById("sb-datei"), + hatSpeichern: !!document.getElementById("sb-speichern"), + abschnitte: [...document.querySelectorAll(".team-gruppe__name")].map((x) => x.textContent.trim()), + })); + ok(eigen.name === name, ` sein Steckbrief zeigt den Namen (${eigen.name})`); + ok(eigen.hatDatei && eigen.hatSpeichern, + " mit Feld fuers Bild und Speichern-Knopf"); + melde(` Abschnitte dort: ${eigen.abschnitte.join(", ") || "(keine)"}`); + ok(kaputt.length === 0, + ` kein Skriptfehler${kaputt.length ? ": " + kaputt.join(" | ") : ""}`); + await ctx.close(); + } + } finally { + await browser.close(); + } + } +} + +melde(""); +melde(`${geprueft} Pruefungen, ${fehler} Fehler`); +melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Rest */ } +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-kachelraster.mjs b/server/pruef-kachelraster.mjs index 852cb33f..9e3837b9 100644 --- a/server/pruef-kachelraster.mjs +++ b/server/pruef-kachelraster.mjs @@ -112,7 +112,26 @@ const browser = await chromium.launch(); async function messen(kachel, code, wer) { const keks = await anmelden(kachel, code); - const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } }); + /* OHNE EINBLEND-ANIMATION MESSEN (19.09.2026). + + Die Kacheln laufen gestaffelt ein (`kachel-ein`, Verzoegerung je + Laufnummer). Waehrend das laeuft, stehen sie noch NICHT an ihrem + Platz -- und diese Pruefung zaehlt Reihen ueber die + Y-Koordinate. Das feste `waitForTimeout(500)` reichte, solange es + acht Kacheln waren; mit einer Gruppe mehr verschiebt sich die + Staffelung, und die Messung fand vier Reihen, wo drei sind. Das + Bildschirmfoto derselben Seite (1200 ms spaeter) zeigte ein + makelloses Raster. + + Eine Wartezeit zu erhoehen waere derselbe Fehler mit einer + groesseren Zahl. `reducedMotion` schaltet die Animation ganz ab -- + die CSS-Regel steht ohnehin in einer + `prefers-reduced-motion: no-preference`-Abfrage. Damit misst die + Pruefung den Endzustand, und zwar immer. */ + const kontext = await browser.newContext({ + viewport: { width: 1280, height: 1000 }, + reducedMotion: "reduce", + }); await kontext.addCookies(keks.split("; ").map((k) => { const [name, ...rest] = k.split("="); return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" }; diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs index 170e6afd..8a39fafb 100644 --- a/server/pruef-start-ansicht.mjs +++ b/server/pruef-start-ansicht.mjs @@ -255,8 +255,27 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) { Sie hat deshalb ausdruecklich KEIN `gruppeNach` -- eine Gruppe ohne Angabe haengt hinten an, und das ist hier die richtige Stelle. */ + /* ACHT SEIT DEM 19.09.2026 -- "Moderation" kam dazu. + + Filipe: "ich will das oben die sachen fuer dogfather sind, dan die + sachen fuer modis und dan community bereich." + + Die Moderationskachel trug bis dahin dieselbe Gruppe wie die + sieben Bretter und verschwand deshalb HINTER ihnen. Jetzt hat sie + eine eigene Gruppe und steht davor -- wer moderiert, muss nicht an + der ganzen Community vorbeiscrollen, um an seine Arbeit zu kommen. + + DIE BEGRUENDUNG OBEN GILT WEITER: Die Community steht trotzdem + ganz unten, denn dort stehen Namen und Saetze von Zuschauern. Die + Moderation davor ist kein Widerspruch dazu -- sie zeigt Vorgaenge, + keine Beitraege. + + Und die Arbeitsgruppen behalten ihre gewachsene Reihenfolge: Die + Sortierregel in start.js schiebt ausdruecklich NUR die drei + hinteren ans Ende. Ein erster Versuch schrieb auch die vorderen + fest und hat diese Prueflinie sofort rot gemacht -- zu Recht. */ const ERWARTETE_GRUPPEN = ["Rund um das Team", "Täglich", "Rund um den Creator", - "Team & System", "Team Dogi", "Entwicklung & Nachwuchs", "Community"]; + "Team & System", "Team Dogi", "Entwicklung & Nachwuchs", "Moderation", "Community"]; ok(JSON.stringify(r.gruppen.map((g) => g.name)) === JSON.stringify(ERWARTETE_GRUPPEN), `${ERWARTETE_GRUPPEN.length} Gruppen in dieser Reihenfolge (${r.gruppen.map((g) => g.name).join(" · ")})`); /* WIE VIELE KACHELN? Nicht abgeschrieben, sondern aus bereiche.js @@ -696,7 +715,40 @@ console.log("\n=== Das Licht folgt dem Zeiger"); der haeufigste Fall. */ await seite.mouse.wheel(0, 600); await seite.waitForTimeout(450); - ok(await anzahlAn() === 0, `nach dem Scrollen ist es aus (${await anzahlAn()})`); + /* GEFRAGT WIRD, WAS GEMEINT IST -- nicht, was bei einer bestimmten + Kachelzahl herauskam (19.09.2026). + + Hier stand `anzahlAn() === 0`, mit der stillen Annahme: nach 600 + px liegt unter dem Zeiger keine Kachel mehr. Das galt, solange + die Seite so lang war wie an dem Tag, an dem die Zeile + geschrieben wurde. Am 19.09. kam eine Gruppe dazu -- danach lag + dort eine ANDERE Kachel, sie leuchtete zu Recht, und die + Pruefung meldete einen Fehler am Licht, obwohl das Licht genau + das tat, was es soll. + + Dieselbe Falle wie die feste Umbruchschwelle in der Kopfleiste: + eine Zahl, die eine Rechnung von gestern festhaelt. Also wird + jetzt gefragt, was wirklich gemeint ist: Die Kachel, die VORHER + unter dem Zeiger lag, darf danach nicht mehr leuchten -- und + wenn dort gar keine mehr liegt, darf ueberhaupt nichts leuchten. */ + const unterZeiger = await seite.evaluate(([x, y]) => { + const e = document.elementFromPoint(x, y); + return !!(e && e.closest(".kachel")); + }, [k2.x + k2.width / 2, k2.y + k2.height / 2]); + + /* Das Merkmal heisst `data-licht="an"` -- nachgesehen bei + `anzahlAn` ein paar Zeilen weiter oben, nicht geraten. */ + const alteLeuchtet = await seite.evaluate(() => + [...document.querySelectorAll(".kachel")][2]?.dataset.licht === "an"); + + if (unterZeiger) { + ok(!alteLeuchtet, + "nach dem Scrollen leuchtet die alte Kachel nicht mehr" + + " (unter dem Zeiger liegt jetzt eine andere)"); + } else { + ok(await anzahlAn() === 0, + `nach dem Scrollen ist es aus (${await anzahlAn()}, keine Kachel unter dem Zeiger)`); + } /* WIEDER HOCH. Ohne das lagen die Kacheln fuer alle folgenden Pruefungen 600 px weiter oben -- die naechste Pruefung bewegte den Zeiger dann ins Leere und meldete einen Fehler, den es nicht gab. */ diff --git a/server/rechte.js b/server/rechte.js index 7f002f2e..92c64b09 100644 --- a/server/rechte.js +++ b/server/rechte.js @@ -218,7 +218,22 @@ export const SEITEN = { Profil" und fuehrte auf eine Seite, deren Schnittstelle mit 404 antwortet, weil sie ausdruecklich nur Creator kennt. Gefunden vom neuen Kachel-Durchgang in pruef-rollen. */ - "/workspace/steckbrief.html": ["spicy", "admin", "manager", "scout", "creator", "hand", "modi"], + /* JEDER MIT EINEM ZUGANG HAT EINEN STECKBRIEF (19.09.2026). + + Filipe: "jeder soll ein steckbrief haben, jeder der ein account + hat, rechte hand, modis und community, jeder soll genau wie ich + foto und so hinzufuegen koennen." + + Vorher fehlte hier "gast" -- und damit ausgerechnet die groesste + Gruppe. Die Seite selbst konnte es die ganze Zeit: `/mein` fragt + nach `req.person.id` und kennt gar keine Rolle. Es fehlte nur die + Tuer. + + WAS EIN MITGLIED DORT SIEHT, ENTSCHEIDET NICHT DIESE ZEILE, + sondern `sichtbareIds` in workspace-steckbrief.js: Der eigene + Eintrag, DogFather, und was die Sichtbarkeitsregeln sonst + hergeben. Eine Seite ist ein Weg, keine Schranke. */ + "/workspace/steckbrief.html": ["spicy", "admin", "manager", "scout", "creator", "hand", "modi", "gast"], "/workspace/kalender.html": [...ALLE], "/workspace/dateien.html": [...ALLE], /* 'modi' kam am 10.09.2026 dazu -- und es war ein echter Fund: Ohne diff --git a/server/workspace-steckbrief.js b/server/workspace-steckbrief.js index f19be566..a6138e41 100644 --- a/server/workspace-steckbrief.js +++ b/server/workspace-steckbrief.js @@ -133,15 +133,58 @@ const SPALTEN = `p.id, p.name, p.rolle, p.aktiv, p.erstellt, p.letzter_login, /** Eine Zeile aus der Datenbank in das, was nach außen geht. Die Kanäle * kommen als fertige Verweise mit -- damit die Oberfläche die Adressen * nicht ein zweites Mal zusammenbauen muss (und dabei anders). */ +/* WER GEHOERT IN WELCHEN ABSCHNITT (19.09.2026). + + DIE ZUORDNUNG STEHT HIER UND NICHT IM BROWSER, aus zwei Gruenden. + + Der erste ist ein Fehler, den sie behebt: In assets/js/steckbrief.js + stand eine Liste aus drei Gruppen -- Management, Scouting, Creator. + Wer eine andere Rolle hatte, passte in keine und verschwand + LAUTLOS aus der Uebersicht. Betroffen waren ausgerechnet die + rechte Hand und die Modis: Ein Modi, der die Seite oeffnete, sah + seine eigenen Leute nicht. + + Der zweite ist die Hausregel: Rollennamen der Community gehoeren + nicht in eine Datei, die jeder Besucher herunterladen kann. Eine + Liste ['modi','hand','gast'] im Browser waere genau das. + + OHNE ABSCHNITT GIBT ES EINEN SAMMELPLATZ. Kommt morgen eine Rolle + dazu und wird hier vergessen, steht sie unter „Weitere" -- sichtbar + und mit einem schiefen Namen. Das ist besser als lautlos weg. */ +const ABSCHNITTE = { + admin: { name: "Leitung", text: "Wer freigibt und entscheidet." }, + manager: { name: "Leitung", text: "Wer freigibt und entscheidet." }, + hand: { name: "Team Dogi", text: "Wer den Laden mit am Laufen hält." }, + modi: { name: "Moderation", text: "Wer im Treff und im Stream nach dem Rechten sieht." }, + scout: { name: "Scouting", text: "Sie finden neue Creator und begleiten den Anfang." }, + creator: { name: "Creator", text: "Die Leute, um die es geht." }, + gast: { name: "Community", text: "Die Leute, die zuschauen und mitmachen." }, + spicy: { name: "Leitung", text: "Wer freigibt und entscheidet." }, +}; +const ABSCHNITT_SONST = { name: "Weitere", text: "" }; + +/** Welcher Abschnitt gehoert zu dieser Rolle? + * + * Ausgeleitet, damit die Pruefung die GEGENPROBE machen kann: Eine + * Rolle, die hier fehlt, muss sichtbar unter „Weitere" landen und darf + * nicht verschwinden. Ohne diesen Zugang waere „niemand faellt heraus" + * eine Behauptung ueber die fuenf Rollen, die es heute gibt. */ +export function abschnittFuer(rolle) { + return ABSCHNITTE[rolle] || ABSCHNITT_SONST; +} + function hinaus(z) { const kanaele = []; for (const [schluessel, k] of Object.entries(KANAELE)) { const handle = z[schluessel]; if (handle) kanaele.push({ art: schluessel, name: k.name, handle, adresse: k.adresse(handle) }); } + const abschnitt = ABSCHNITTE[z.rolle] || ABSCHNITT_SONST; return { id: z.id, name: z.name, rolle: z.rolle, rolle_name: ROLLEN_NAME[z.rolle] ?? z.rolle, + abschnitt: abschnitt.name, + abschnitt_text: abschnitt.text, aktiv: !!z.aktiv, erstellt: z.erstellt, letzter_login: z.letzter_login, bild: z.bild ? `/workspace/api/steckbrief/bild/${z.bild}` : null, ueber_mich: z.ueber_mich || null, diff --git a/server/workspace.js b/server/workspace.js index 941115b0..0ca66858 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -976,8 +976,26 @@ const TREFF_BEREICHE = [ Eintraegen, sondern eine Seite. Das Ziel ist deshalb ein Dateiname und kein "bereich.html?b=...", und die Ableitung weiter unten nimmt sie von selbst nicht mit. */ +/* EIGENE GRUPPE, NICHT DIE DER COMMUNITY (19.09.2026). + + Filipe: "ich will das oben die sachen fuer dogfather sind, dan die + sachen fuer modis und dan community bereich." + + Bis heute trug diese Kachel `...TREFF_GRUPPE` -- sie landete damit + IN der Gruppe \u201eCommunity" und dort, weil sie zuletzt angehaengt + wird, hinter allen sieben Brettern. Wer moderiert, musste also an + der ganzen Community vorbeiscrollen, um an seine Arbeit zu kommen. + + Getrennt ist es auch inhaltlich richtig: Die sieben Bretter sind der + Raum der Zuschauer. Diese Kachel ist Arbeit AN diesem Raum -- und + die sieht nur, wer moderiert. */ +const MODERATION_GRUPPE = { + gruppe: "Moderation", + gruppeUnter: "Die Arbeit am Treff \u2013 sieht nur, wer moderiert", +}; + const TREFF_MODERATION_KACHEL = { - ...TREFF_GRUPPE, + ...MODERATION_GRUPPE, name: "Meldungen & Ma\u00dfnahmen", unter: "Was gemeldet wurde \u2013 und was daraus wurde", /* EIGENES ZEICHEN, NICHT DASSELBE WIE "REGELN & HILFE" (17.09.2026). @@ -1056,11 +1074,34 @@ function nurOffeneKacheln(liste, person) { }); } +/* JEDER HAT EINEN STECKBRIEF -- AUCH DIE COMMUNITY (19.09.2026). + + Filipe: "jeder soll ein steckbrief haben, jeder der ein account hat + ... jeder soll genau wie ich foto und so hinzufuegen koennen." + + Das Team hatte die Kachel laengst (sie steht in MODI_BEREICHE unter + „Für dich"), die Community nicht -- und damit die groesste Gruppe + ueberhaupt. Die Seite selbst konnte es die ganze Zeit: `/mein` fragt + nach der eigenen Nummer und kennt gar keine Rolle. + + EIGENE FASSUNG DES UNTERTEXTS: „Deine Seite im Team" waere fuer ein + Mitglied falsch -- es ist nicht im Team. Der Steckbrief ist fuer sie + das, was man von sich zeigt. */ +const STECKBRIEF_KACHEL_TREFF = { + gruppe: "Für dich", gruppeUnter: "Deine eigene Seite", + name: "Mein Steckbrief", unter: "Dein Bild und deine Kanäle", + zeichen: "steckbrief", ton: 10, ziel: "steckbrief.html", szene: "halle", +}; + +/* Die Community: der Treff und ihre eigene Seite. Sonst nichts. */ +const TREFF_MIT_EIGENEM = [...TREFF_BEREICHE, STECKBRIEF_KACHEL_TREFF]; + export function bereicheFuer(person) { if (person?.rolle === "modi") return MODI_MIT_TREFF; if (person?.rolle === "hand") return HAND_MIT_TREFF; - /* DIE COMMUNITY SIEHT DEN TREFF UND SONST NICHTS (11.09.2026). */ - if (person?.rolle === "gast") return TREFF_BEREICHE; + /* DIE COMMUNITY SIEHT DEN TREFF UND SONST NICHTS (11.09.2026) -- + seit dem 19.09. plus den eigenen Steckbrief. */ + if (person?.rolle === "gast") return TREFF_MIT_EIGENEM; /* AUF DER ADRESSE VON TEAM DOGI SIEHT AUCH DOGFATHER DIE KACHELN DES TEAMS (10.09.2026). diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 5e2c28be..dffc98c1 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -5,9 +5,9 @@ Anruf-Probe · Team Dogi - - - + + +