diff --git a/server/pruef-backstage-import.mjs b/server/pruef-backstage-import.mjs index 62b0add0..4d873826 100644 --- a/server/pruef-backstage-import.mjs +++ b/server/pruef-backstage-import.mjs @@ -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(); } diff --git a/server/workspace.js b/server/workspace.js index 07d4a75d..44c20f5c 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -3202,13 +3202,29 @@ const GESCHUETZT = { Lesezeichen hat, waere weiterhin hineingekommen. Beides gehoert zusammen: bereiche.js zeigt sie nur noch DogFather, diese Zeile laesst nur ihn hinein. */ - /* NUR DogFather -- auch nicht Spicy Media (07.09.2026, zweite - Praezisierung). Beim Anlegen der Rolle stand hier ["spicy","admin"], - weil Spicy Media "dieselben Rechte ausser Automationen" bekommen - sollte. Filipe hat es an der laufenden Seite gesehen und - widersprochen: "bei der spicy rolle, die zahlen diese kategorie - nicht sehen." */ - "/workspace/leistung.html": ["admin"], + /* JETZT WIEDER FUER ALLE (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." + + Die Zeile stand seit dem 07.09.2026 auf ["admin"] -- erst "nimm + die kategorie zahlen bei jedem weg", dann die Praezisierung, dass + auch Spicy Media sie nicht sieht. Beides ist damit aufgehoben. + + AUSDRUECKLICH ALS LISTE, NICHT ALS `null`. `null` hiesse "jede + angemeldete Rolle" -- und damit automatisch auch jede Rolle, die + es noch gar nicht gibt. Genau das ist am 10.09. bei content.html + schiefgegangen. Hier stehen die fuenf, die Filipe genannt hat. + + WER WAS DARF, steht NICHT hier. Diese Zeile oeffnet nur die Tuer. + Dahinter gilt weiterhin: sichtbareCreatorIds() entscheidet, WESSEN + Zahlen jemand sieht (ein Creator sieht genau sich selbst), und + darfEintragen() entscheidet, wer schreiben darf (Leitung + Scout, + also NICHT der Creator). Beides war schon vorher so gebaut und + bleibt unangetastet -- es war nur niemand da, der es benutzen + konnte. */ + "/workspace/leistung.html": ["spicy", "admin", "manager", "scout", "creator"], /* NUR DogFather (02.09.2026). Die Rollenauswahl verspricht das seit jeher ("Manager -- dieselben Rechte, ausser der Personenverwaltung"), die Schranke hielt sich nur nicht daran. diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 04d4339f..a2dd22df 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609111350", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609111404", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609111350", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609111404", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609111350", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609111404", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609111350", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609111404", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/js/bereiche.js b/workspace/assets/js/bereiche.js index 7f172f7f..22d4c30a 100644 --- a/workspace/assets/js/bereiche.js +++ b/workspace/assets/js/bereiche.js @@ -455,15 +455,23 @@ die Frage, welcher der richtige ist. */ { name: 'Mein Profil', unter: 'Dein Bild, deine Ziele und Pläne', zeichen: 'profil', ton: 20, rollen: ['creator'], ziel: 'profil.html', szene: 'halle' }, - /* NUR DOGFATHER (07.09.2026, Wunsch Filipe: "nimm die kategorie - zahlen bei jedem weg, nur die rolle dogfather soll die - sehen"). Die Kachel verschwindet damit fuer Manager, Scouts - und Creator -- die SEITE bleibt fuer sie gesperrt, das regelt - weiterhin die Rechteliste in workspace.js. Eine Kachel - wegzunehmen, ohne die Seite zu schliessen, waere nur ein - versteckter Weg und keine Regel. */ + /* WIEDER FUER ALLE FUENF (11.09.2026). Filipe: "jetzt soll jede + rolle diese kachel sehen. spicy, dogfather, manager, scout + und creator." Damit ist die Sperre vom 07.09. aufgehoben. + + Die Kachel sieht fuer alle gleich aus, dahinter ist sie es + nicht: Ein Creator findet dort NUR seine eigenen Zahlen und + keinen einzigen Knopf zum Eintragen -- das entscheidet nicht + diese Zeile, sondern sichtbareCreatorIds() und + darfEintragen() auf dem Server. + + Kachel und Tuer gehoeren zusammen: Diese Zeile ist der Weg, + die Rechteliste in workspace.js ist die Schranke. Nur eines + von beiden zu aendern ergibt entweder eine Kachel, die in + eine Absage fuehrt, oder einen Weg, den man nur kennen + muss. */ { name: 'Zahlen', unter: 'Diamanten, LIVE-Tage, Verweildauer', zeichen: 'zahlen', ton: 2, - rollen: ['admin'], ziel: 'leistung.html', szene: 'arena' }, + rollen: ['spicy', 'admin', 'manager', 'scout', 'creator'], ziel: 'leistung.html', szene: 'arena' }, { name: 'Start-Check', unter: 'Erstanalyse, 16 Punkte', zeichen: 'startcheck', ton: 16, rollen: ['spicy', 'admin', 'manager', 'creator', 'scout'], ziel: 'startcheck.html', szene: 'wald' }, { name: 'LIVE-Analyse', unter: 'Vorher, während, danach', zeichen: 'live', ton: 3, diff --git a/workspace/assets/js/leistung.js b/workspace/assets/js/leistung.js index 5b7f49e7..7ac8ce18 100644 --- a/workspace/assets/js/leistung.js +++ b/workspace/assets/js/leistung.js @@ -175,19 +175,46 @@ ziel.textContent = ''; const z = stand?.ziele; $('ziele-aendern').hidden = !darfEintragen(); - /* Der Backstage-Weg gilt fuer ALLE Creator auf einmal -- deshalb - nur fuer die Leitung, nicht fuer einen Scout, der ohnehin nur - seine eigenen sieht. Der Server prueft dasselbe; hier geht es - darum, niemandem einen Knopf hinzustellen, der eine Absage - bringt. */ - $('netz-oeffnen').hidden = !LEITUNG.has(ich?.rolle); - /* Die Datei gehoert EINEM Creator -- deshalb sichtbar fuer alle, - die fuer ihn eintragen duerfen, also auch fuer seinen Scout. */ - $('tt-oeffnen').hidden = !darfEintragen(); - /* Der Hinweis erscheint nur, wenn wenigstens einer der beiden - Knoepfe da ist -- eine Erklaerung zu Knoepfen, die man nicht - sieht, ist Rauschen. */ - $('quellen-hinweis').hidden = $('tt-oeffnen').hidden && $('netz-oeffnen').hidden; + + /* ALLE EINTRAG-KNOEPFE AN EINER STELLE (11.09.2026). + Seit heute oeffnet sich diese Seite fuer jede Rolle -- Filipe: + "jetzt soll jede rolle diese kachel sehen ... aber die creator + kriegen dan quasi nur ihre daten zu sehen." + + GEMESSEN, NICHT ANGENOMMEN: Beim Umbau fiel auf, dass + 'import-oeffnen' als einziger Knopf NIE eine Rechteregel hatte. + Das war folgenlos, solange nur DogFather hereinkam. Ab heute + haette ein Creator dort "Aus Datei einlesen" gesehen und eine + Absage bekommen. Genau deshalb steht die Liste jetzt hier: Wer + einen Knopf hinzufuegt, sieht die Regel daneben und kann sie + nicht vergessen. */ + const KNOEPFE = [ + /* Eigene Datei mit Tageszahlen -- wer eintragen darf, darf das. */ + ['import-oeffnen', darfEintragen()], + /* Der Backstage-Weg gilt fuer ALLE Creator auf einmal -- deshalb + nur fuer die Leitung, nicht fuer einen Scout, der ohnehin nur + seine eigenen sieht. Der Server prueft dasselbe (netzDarf); + hier geht es darum, niemandem einen Knopf hinzustellen, der + eine Absage bringt. */ + ['netz-oeffnen', LEITUNG.has(ich?.rolle)], + /* Die Datei gehoert EINEM Creator -- deshalb sichtbar fuer alle, + die fuer ihn eintragen duerfen, also auch fuer seinen Scout. */ + ['tt-oeffnen', darfEintragen()], + ]; + for (const [knopf, darf] of KNOEPFE) $(knopf).hidden = !darf; + + /* Jeder Erklaersatz haengt an SEINEM Knopf, nicht am Block. Ein + Scout sieht nur die TikTok-Datei -- ihm die Backstage-Tabelle zu + erklaeren, an die er gar nicht kommt, ist keine Hilfe, sondern + eine Absage, die er nicht gesucht hat. Und ein Creator sieht + keinen von beiden, also auch keinen Satz. Abgeleitet statt + zweimal aufgeschrieben: kommt ein dritter Weg dazu, stimmt es + weiter. */ + for (const [satz, knopf] of [['quelle-netz', 'netz-oeffnen'], + ['quelle-tt', 'tt-oeffnen']]) { + $(satz).hidden = $(knopf).hidden; + } + $('quellen-hinweis').hidden = $('quelle-netz').hidden && $('quelle-tt').hidden; if (!z || (!z.diamanten && !z.tage && !z.verweildauer)) { ziel.append(el('p', 'leistung__leer', diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 0a6535c0..d522dbc8 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - +
@@ -252,10 +252,10 @@ - - - - - + + + + +