Files
dogfather-universe/server/pruef-workspace-seiten.mjs
T
DogFatherGitandClaude Opus 5 2bebab156b Profilbild gebaendigt, Steckbrief von der Creator-Akte getrennt
ZWEI FEHLER, DIE FILIPE GESEHEN HAT.

1. DAS PROFILBILD LAG UEBER DER HALBEN SEITE. Als Patrick sein Bild
   hochlud, zog sich ein roter Balken quer ueber die Startseite.

   Ursache: Die CSS-Regel .wer__bild hat GEFEHLT. Das Element wurde in
   kopf.js erzeugt, die Regel dazu nie geschrieben -- und ein <img> ohne
   Groessenangabe nimmt seine natuerliche Groesse an, bei einem Handyfoto
   also mehrere tausend Pixel. Dazu fehlte overflow: hidden am Traeger.

   WARUM KEINE PRUEFUNG DAS GEFUNDEN HAT: Sie lud ein 1x1-Pixel-PNG hoch.
   Klein, schnell, von Hand gebaut -- und voellig unfaehig, irgendetwas
   zu ueberdecken. Die Pruefung war gruen, der Fehler war da, und gesehen
   hat ihn der Nutzer. Sie arbeitet jetzt mit einem 1200x1200-Bild, also
   in der Groesse, die wirklich hochgeladen wird, und misst danach: Bleibt
   das Bild in seinem 28-px-Feld, ragt es irgendwo heraus, laeuft die
   Seite ueber. Gegenprobe gemacht -- ohne die Regel meldet sie
   1200x1200 in 28x28 und 918 px Ueberlauf.

2. ZWEI PERSONEN AUF EINEM BILDSCHIRM. Auf der Profilseite stand oben
   "Profil: SpongBobSchwammKopf" und mittendrin "MEIN PROFIL: Dogfather".
   Niemand konnte sagen, welche Angabe zu wem gehoert.

   Es sind auch wirklich zwei verschiedene Dinge:
     MEIN STECKBRIEF  gehoert MIR -- Bild, ein Satz ueber mich, meine
                      Kanaele. Fuehre ich selbst.
     CREATOR-PROFILE  die BETREUUNGSAKTE eines anderen Menschen --
                      Ziele, 90-Tage-Plan, interne Notizen. Fuehren die
                      Betreuer.

   Scouts, Manager und DogFather haben jetzt zwei getrennte Kacheln und
   eine eigene Seite (steckbrief.html, serverseitig geschuetzt). Ein
   Creator behaelt beides zusammen -- er hat nur eine Seite und sieht
   dort ausschliesslich sich selbst. Die Logik liegt in einer eigenen
   Datei statt hinten an profil.js: Sie gehoert der angemeldeten Person,
   nicht der Akte.

ACHTZEHN KACHELN, ACHTZEHN FARBEN. Die neue Kachel haette sich ihren
Farbton mit den Creator-Profilen geteilt -- zwei Nachbarn in derselben
Farbe. Statt eine Farbe dazuzuerfinden, wurde tools/kachel-farben.mjs
fuer 18 Winkel neu gerechnet, samt der neuen Nachbarschaft im Raster.
Der kleinste Abstand zweier Nachbarn liegt weiterhin bei 100 Grad.
Gefunden hat das die Startseitenpruefung ("17 Farben auf 18 Kacheln").

Die Trennung wird jetzt fuer JEDE Rolle geprueft: welche Kacheln sie
sieht, dass auf der Akte kein fremder Steckbrief steht, und dass die
eigene Seite die richtige Person zeigt. Eine alte Pruefung, die das
Gegenteil verlangte, wurde ersetzt statt stehen gelassen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-01 15:04:23 +02:00

150 lines
8.1 KiB
JavaScript

/* Kurzpruefung: Steht die Kopfleiste auf dem Handy ueber -- und zwar
auf ALLEN Seiten oder nur auf der neuen? Beantwortet die Frage, ob
ich einen eigenen Fehler gebaut oder einen alten gefunden habe. */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-kopf-"));
process.env.WORKSPACE_DB = join(ordner, "pruef.sqlite");
process.env.PORT = "4178";
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
process.env.SITE_ACCESS_CODE_VANVAN = "VANVAN-TEST-1";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const echteCookie = express.response.cookie;
express.response.cookie = function (n, w, o) {
return echteCookie.call(this, n, w, { ...(o || {}), secure: false });
};
await import("./index.js");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4178";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 150_000).unref?.();
await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "x" }),
}).catch(() => {});
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const salt = randomBytes(16).toString("hex");
const hash = scryptSync("CODE-DOGI-0001", 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("Test-DogFather", "admin", hash, salt, 32768, new Date().toISOString());
d.close();
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
/* Alle Seiten, die eine angemeldete Person zu sehen bekommt. Geprueft
wird auf beiden Groessen, weil die geteilten Klassen (.kopf-zeile,
.inhalt--breit, .leise) am 31.08.2026 von aufgaben.css nach start.css
gewandert sind -- faellt dabei etwas herunter, sieht man es hier. */
const SEITEN = ["start.html", "uebersicht.html", "aufgaben.html", "kalender.html",
"personen.html", "profil.html", "steckbrief.html", "wissen.html", "dateien.html",
"calls.html", "startcheck.html", "report.html", "scouting.html",
"automation.html", "content.html", "bereich.html?b=live"];
let fehler = 0;
for (const [breite, hoehe, wie] of [[1440, 1000, "Computer"], [390, 844, "Handy"]]) {
console.log(`\n=== ${wie} (${breite}px)`);
const kontext = await browser.newContext({ viewport: { width: breite, height: hoehe } });
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
seite.on("pageerror", (e) => konsole.push("pageerror: " + e.message));
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
for (const s of SEITEN) {
konsole.length = 0;
await seite.goto(BASIS + "/workspace/" + s, { waitUntil: "networkidle" });
await seite.waitForTimeout(300);
const r = await seite.evaluate(() => ({
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
schuld: [...document.querySelectorAll("body *")]
.filter((e) => e.getBoundingClientRect().right > window.innerWidth + 1)
.map((e) => (e.tagName + "." + e.className).slice(0, 40)).slice(0, 4),
/* Die verschobenen Klassen muessen weiterhin greifen. .inhalt--breit
setzt 1240px -- kommt nur 880px an, ist die Regel verloren. */
breit: document.querySelector(".inhalt--breit")
? getComputedStyle(document.querySelector(".inhalt--breit")).maxWidth : null,
kopfzeile: document.querySelector(".kopf-zeile")
? getComputedStyle(document.querySelector(".kopf-zeile")).display : null,
abmelden: (() => {
const a = document.querySelector(".abmelden")?.getBoundingClientRect();
return a ? a.right <= window.innerWidth && a.left >= 0 : null;
})(),
/* Der Schriftzug oben links wird von kopf.js in Rang aufgeteilt.
Auf den Unterseiten ist der vordere Teil ein VERWEIS zurück zur
Startseite -- der muss die Aufteilung überleben, sonst ist der
Rückweg weg. Genau das wäre beim Zerlegen von Textknoten leicht
zu übersehen. */
markeHaupt: document.querySelector(".kopfleiste .marke__haupt")?.textContent
.replace(/ /g, " ") || null,
markeLeise: document.querySelector(".kopfleiste .marke__leise")?.textContent
.replace(/ /g, " ") || null,
markeRaute: document.querySelectorAll(".kopfleiste .marke__raute").length,
/* Ist der vordere Teil auf einer Unterseite noch ein klickbarer
Verweis auf die Startseite? */
rueckweg: document.querySelector(".kopfleiste .marke__haupt")?.tagName === "A"
? document.querySelector(".kopfleiste .marke__haupt").getAttribute("href") : null,
/* Jede Unterseite trägt Zeichen und Farbton ihrer Kachel. Beides
kommt aus bereiche.js, derselben Liste wie die Kachel -- fehlt
eins davon, ist die Zuordnung gerissen und die Seite sieht
wieder aus wie jede andere. */
ton: document.body.dataset.ton || null,
/* Jede Seite hat ihre eigene Bühne aus Filipes Bildern. Geprüft
wird nicht das Merkmal, sondern das TATSÄCHLICH geladene Bild --
ein data-buehne ohne passende CSS-Regel wäre still wirkungslos
und niemand würde es merken. */
buehne: document.body.dataset.buehne || null,
buehneBild: (getComputedStyle(document.body, "::before").backgroundImage
.match(/buehne-([a-z]+)-(breit|schmal)/) || [])[1] || null,
kopfZeichen: document.querySelectorAll(".kopf-zeichen__svg path").length,
kopfZeichenFarbe: document.querySelector(".kopf-zeichen__svg")
? getComputedStyle(document.querySelector(".kopf-zeichen__svg")).stroke : null,
wasserzeichen: document.querySelectorAll(".kopf-wasserzeichen__svg path").length,
}));
const istStart = s === "start.html";
const markeGut = r.markeHaupt && r.markeLeise && r.markeRaute === 1
&& (istStart || r.rueckweg === "start.html");
/* Auf der Startseite gibt es keinen Seitenkopf -- dort sind die
Kacheln selbst der Kopf. Überall sonst ist er Pflicht. */
const kopfGut = istStart
|| (r.ton !== null && r.kopfZeichen > 0 && r.wasserzeichen > 0);
/* Die Bühne muss gesetzt sein UND als Bild wirklich ankommen. */
const buehneGut = istStart
? r.buehneBild === "studio"
: (r.buehne !== null && r.buehneBild === r.buehne);
const gut = r.ueber <= 0 && konsole.length === 0 && r.abmelden !== false
&& markeGut && kopfGut && buehneGut
&& (r.breit === null || r.breit === "1240px")
&& (r.kopfzeile === null || r.kopfzeile === "flex");
if (!gut) fehler++;
console.log(` ${gut ? "ok " : "FEHL"} ${String(s).padEnd(20)}`
+ `über=${String(r.ueber).padStart(3)}px abmelden=${r.abmelden}`
+ ` marke=${r.markeHaupt ? `"${r.markeHaupt}"◆"${r.markeLeise}"` : "FEHLT"}`
+ `${istStart ? "" : ` zurück=${r.rueckweg || "FEHLT"}`}`
+ `${istStart ? "" : ` ton=${r.ton || "FEHLT"} zeichen=${r.kopfZeichen}/${r.wasserzeichen}`}`
+ ` bühne=${r.buehneBild || "FEHLT"}${r.buehne && r.buehneBild !== r.buehne ? ` (gewollt: ${r.buehne})` : ""}`
+ ` breit=${r.breit}`
+ (konsole.length ? " KONSOLE: " + konsole.join(" | ") : "")
+ (r.schuld.length ? " -> " + r.schuld.join(" | ") : ""));
}
await kontext.close();
}
await browser.close();
console.log(`\n${fehler === 0 ? "Alles in Ordnung." : fehler + " Seite(n) fehlerhaft."}`);
try { rmSync(ordner, { recursive: true, force: true }); } catch {}
process.exit(fehler === 0 ? 0 : 1);