diff --git a/server/pruef-kopfleiste-farbe.mjs b/server/pruef-kopfleiste-farbe.mjs new file mode 100644 index 00000000..882af0be --- /dev/null +++ b/server/pruef-kopfleiste-farbe.mjs @@ -0,0 +1,225 @@ +/* ===================================================================== + JEDE SEITE TRÄGT IHRE FARBE — auch ganz oben in der Leiste + ===================================================================== + + Filipe am 22.09.2026: + + „je nachdem auf welcher seite ich bin wechseln gaaanz oben in der + leiste gewissene symbole und sachen mit ... ich will das auf + jeder seite ... nicht mehr wie auf screen3 sondern genau gleich + wie da." + + Sein Vorbild war die Highlights-Seite. Und das ist der Schlüssel zum + Befund: Highlights ist `bereich.html`, und `bereich.html` ist eine + von DREI Seiten, die `bereich.css` laden. Dort stand die Regel, die + aus `data-ton` eine Akzentfarbe macht. + + Gemessen: `kopf.js` setzt `data-ton` am auf jeder Seite. Auf + zweiunddreißig von fünfunddreißig las es niemand — die Farbe war da + und kam nicht an. Seit dem 22.09. steht die Regel in `start.css`, + und die liegt auf allen fünfunddreißig. + + --------------------------------------------------------------------- + WAS HIER GEMESSEN WIRD — und warum nicht der Quelltext + + Ob eine CSS-Regel WIRKT, steht nicht in der Datei. `--akzent` könnte + von einer späteren Regel überschrieben werden, `data-ton` könnte + fehlen, das Haus könnte etwas anderes sagen. Deshalb ein echter + Browser und `getComputedStyle` — die Farbe, die auf dem Schirm + ankommt. + + ZWEI SEITEN MIT VERSCHIEDENEN TÖNEN, und beide Richtungen: + * auf jeder kommt eine Farbe an + * und es ist nicht dieselbe + Ohne den zweiten Teil wäre eine Seite, die überall dieselbe Farbe + zeigt, ebenfalls grün — und genau das war der Zustand vorher. + ===================================================================== */ +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 { execFileSync } from "node:child_process"; +import { createServer as httpsServer } from "node:https"; +import { notbremse } from "./helfer-notbremse.mjs"; +import { eigenerPort } from "./helfer-port.mjs"; + +const CREW = "crew.dogfather-universe.com"; +const PORT = await eigenerPort(import.meta, "pruef-kopfleiste-farbe"); +const ordner = mkdtempSync(join(tmpdir(), "ws-kopffarbe-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +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"); +notbremse(180_000, "pruef-kopfleiste-farbe"); +await new Promise((r) => setTimeout(r, 900)); + +let fehler = 0, geprueft = 0, offen = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; +const unklar = (t) => { offen++; console.log(" -- " + t); }; + +/* ---------- Ein Mensch ------------------------------------------------- */ +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const jetzt = new Date().toISOString(); +const k = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel,wert,geaendert,von) VALUES (?,?,?,NULL)" + + " ON CONFLICT(schluessel) DO NOTHING").run("code_kennung_schluessel", k, jetzt); +{ + 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,code_kennung,aktiv,erstellt)" + + " VALUES (?,?,?,?,?,?,1,?)") + .run("Filipe", "admin", hash, salt, 32768, + createHmac("sha256", k).update("CODE-DOGI-0001").digest("hex"), jetzt); +} +d.close(); + +/* ---------- Der Vorbau ------------------------------------------------- */ +const schl = join(ordner, "p.key"), zert = join(ordner, "p.crt"); +execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-keyout", schl, + "-out", zert, "-days", "2", "-subj", `/CN=${CREW}`, "-addext", `subjectAltName=DNS:${CREW}`], +{ stdio: "ignore" }); +const HP = PORT + 1; +const vorbau = httpsServer({ key: readFileSync(schl), cert: readFileSync(zert) }, (von, nach) => { + const w = httpAnfrage({ host: "127.0.0.1", port: PORT, path: von.url, method: von.method, + headers: { ...von.headers, host: `${CREW}:${HP}` } }, + (a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); }); + von.pipe(w); +}); +await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r)); +const SEITE = `https://${CREW}:${HP}`; + +const pw = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await pw.chromium.launch({ + args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"] }); +const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } }); +const seite = await ctx.newPage(); +const meckern = []; +seite.on("pageerror", (f) => meckern.push(String(f).slice(0, 140))); + +console.log("=== Jede Seite traegt ihre Farbe ==="); +console.log(""); + +await seite.goto(`${SEITE}/workspace/`, { waitUntil: "networkidle" }); +await seite.click('.rolle[data-rolle="admin"]'); +await seite.fill("#code", "CODE-DOGI-0001"); +await seite.click("button[type=submit]"); +await seite.waitForTimeout(900); + +/* WELCHE SEITEN GEMESSEN WERDEN, STEHT NICHT HIER. + ----------------------------------------------------------------- + Die Kacheln der Startseite sagen, welche Seiten es gibt und welchen + Ton jede hat. Eine Liste in dieser Datei waere die, die beim + naechsten Umbau eine Seite nennt, die es nicht mehr gibt -- oder, + schlimmer, die neue nicht kennt. */ +await seite.goto(`${SEITE}/workspace/start.html`, { waitUntil: "networkidle" }); +await seite.waitForTimeout(1800); +/* DIE KACHEL IST DAS
  • , DER VERWEIS LIEGT DARIN. + Beim ersten Anlauf stand hier `.kachel[href]` -- das trifft nichts, + und die Pruefung meldete "0 Kacheln". Das war richtig gemeldet und + trotzdem ein Fehler von mir: Die Struktur steht in start.js + (`li.kachel` mit `a.kachel__link` darin), nachgesehen statt + geraten. */ +const kacheln = await seite.evaluate(() => + [...document.querySelectorAll('li.kachel[data-ton]')] + .map((li) => ({ + ziel: li.querySelector('a')?.getAttribute("href") || null, + ton: li.dataset.ton || null, + })) + .filter((x) => x.ziel && /^[a-z-]+\.html$/.test(x.ziel) && x.ton !== null)); + +ok(kacheln.length >= 4, + `${kacheln.length} Kacheln mit eigenem Ton auf der Startseite gefunden`); + +/* Zwei mit VERSCHIEDENEN Toenen -- ohne Unterschied liesse sich nicht + zeigen, dass die Farbe der Seite folgt und nicht der Leiste. */ +const nachTon = new Map(); +for (const x of kacheln) if (!nachTon.has(x.ton)) nachTon.set(x.ton, x); +const auswahl = [...nachTon.values()].slice(0, 3); +ok(auswahl.length >= 2, + `davon ${auswahl.length} mit verschiedenen Toenen (${auswahl.map((x) => x.ton).join(", ")})`); + +const gemessen = []; +for (const x of auswahl) { + await seite.goto(`${SEITE}/workspace/${x.ziel}`, { waitUntil: "networkidle" }); + await seite.waitForTimeout(1500); + const w = await seite.evaluate(() => { + const koerper = getComputedStyle(document.body); + const leiste = document.querySelector(".kopfleiste"); + return { + adresse: location.pathname.split("/").pop(), + ton: document.body.dataset.ton || null, + akzent: koerper.getPropertyValue("--akzent").trim(), + tonFarbe: koerper.getPropertyValue("--ton").trim(), + leisteDa: !!leiste, + /* Die Kante unter der Leiste -- gemessen an dem, was wirklich + gezeichnet wird, nicht an der Regel im Quelltext. */ + kante: leiste ? getComputedStyle(leiste, "::before").backgroundImage : "", + }; + }); + gemessen.push(w); +} + +console.log(""); +console.log("--- Was auf den Seiten ankommt ---"); +for (const w of gemessen) { + console.log(` ${w.adresse.padEnd(18)} Ton ${String(w.ton).padStart(2)}` + + ` --akzent ${w.akzent}`); +} + +ok(gemessen.length >= 2 && gemessen.every((w) => w.ton !== null), + `auf allen ${gemessen.length} Seiten steht ein Ton am Koerper`); +/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. `every()` auf einem leeren Feld + liefert `true` -- beim ersten Lauf waren drei Zeilen hier gruen, + ohne dass eine einzige Seite gemessen worden war. Genau der Fehler, + vor dem die Hausregel warnt, und er ist mir trotzdem passiert. */ +ok(gemessen.length >= 2 && gemessen.every((w) => w.akzent && w.akzent !== "none"), + `auf allen ${gemessen.length} kommt daraus eine Akzentfarbe an`); + +/* DER ENTSCHEIDENDE VERGLEICH. Vorher war --akzent auf 32 von 35 + Seiten die feste Hausfarbe -- also ueberall dieselbe. */ +const verschieden = new Set(gemessen.map((w) => w.akzent)).size; +ok(gemessen.length >= 2 && verschieden === gemessen.length, + `und sie ist auf jeder Seite eine andere (${verschieden} von ${gemessen.length})`); + +ok(gemessen.length >= 2 && gemessen.every((w) => w.leisteDa), + `die Kopfleiste steht auf allen ${gemessen.length} gemessenen Seiten`); +ok(gemessen.length >= 2 && gemessen.every((w) => /gradient/i.test(w.kante || "")), + "und traegt unten eine Kante in der Farbe der Seite"); + +/* GEGENPROBE: Eine Seite OHNE Ton darf die Hausfarbe behalten. Eine + Regel, die auch dort zuschlaegt, wuerde eine Farbe erfinden. */ +console.log(""); +console.log("--- Gegenprobe ---"); +{ + const ohne = await seite.evaluate(() => { + const vorher = document.body.dataset.ton; + delete document.body.dataset.ton; + const a = getComputedStyle(document.body).getPropertyValue("--akzent").trim(); + if (vorher !== undefined) document.body.dataset.ton = vorher; + return a; + }); + const mit = gemessen.at(-1)?.akzent || null; + ok(!!ohne && !!mit && ohne !== mit, + `ohne Ton gilt wieder die Hausfarbe (${ohne} statt ${mit})`); +} + +ok(meckern.length === 0, `der Browser meldet nichts (${meckern[0] || "still"})`); + +/* ---------- Ende ------------------------------------------------------ */ +await browser.close(); +await new Promise((r) => vorbau.close(r)); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ } + +console.log(""); +console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}` + + ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`); +process.exit(fehler === 0 ? 0 : 1); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 1af70638..4b6ced7f 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +