Files
dogfather-universe/server/pruef-ueberlappung.mjs
T
DogFatherGitandClaude Opus 5 de19684883 Jede Seite erklaert sich selbst -- drei Zeilen, und die dritte rechnet sich aus
Filipe: "ich will in jeder seite auch eine kleine detaillierte aber
schnelle erklaerung kurz und knapps noch zu jeder seite."

DREI ZEILEN AUF JEDER DER 25 SEITEN

  WOFUER    was auf dieser Seite steht
  DU TUST   was man hier konkret macht
  MERKE     nur dort, wo es etwas gibt, das man falsch versteht
  SIEHT     wer diese Seite ueberhaupt oeffnen kann

Offen sichtbar und nicht hinter einem Aufklapper: "schnell" war seine
Bedingung, und eine Erklaerung, die man erst aufklappen muss, ist
genau dann nicht da, wenn man sie braucht.

DIE ZEILE "SIEHT" IST GERECHNET, NICHT GESCHRIEBEN

Sie kommt aus der Rechtetafel -- aus derselben, aus der auch die
Schranke ihre Entscheidung holt. Haette ich sie danebengeschrieben,
waere sie beim ersten Umstellen in "Wer sieht was" still falsch
geworden, in einem Satz, der behauptet, wer etwas sieht. Die Pruefung
stellt die Tafel deshalb wirklich um und sieht nach, ob der Satz
mitwandert.

Die Erklaerung haengt hinter derselben Schranke wie die Seite: Wer sie
nicht oeffnen darf, bekommt 404 -- nicht 403, sonst waere dieser Weg
eine Landkarte des Hauses.

Eingebunden ist sie nicht ueber eine Liste von Dateinamen, sondern
ueber "wer kopf.js laedt": die 26. Seite ist damit automatisch dabei.
Fuenf Seiten haben keine Kopfzeile (Chat, Kalender, Start, Team,
Teamlage) -- dort haengt der Kasten an <main>, sonst waeren
ausgerechnet die haeufigsten Seiten lautlos leer geblieben.

ZWEI EIGENE MAENGEL, GEFUNDEN DURCH HINSEHEN, NICHT DURCH DIE PRUEFUNG

Die erste Fassung war gruen -- und stand mitten auf dem Buehnenbild,
mit einem Lichtfleck hinter "SIEHT". Und "Wofuer" sprach fast woertlich
die Unterzeile darueber nach. Beides misst die Pruefung jetzt: Kontrast
mit dem dritten Ausgang ("steht auf der Buehne" ist kein Ergebnis) und
die Wortueberschneidung mit der Unterzeile.

ZWEI BEFUNDE, DIE SCHON VORHER ROT WAREN

1. pruef-tempo-workspace verglich die UNKOMPRIMIERTE Groesse gegen eine
   Grenze, die fuer die Leitung gedacht war -- waehrend der Kommentar
   daneben "das ist, was wirklich ueber die Leitung ging" behauptete.
   Live steht Caddy davor. Nachgemessen an der echten Adresse:
   start.css 345 KB -> 111 KB zstd. Die Pruefung wog also 1,2 MB, wo
   Filipe 500 KB bekommt. Jetzt wird Komprimierbares im Pruefstand
   gzip-gepackt (115 KB gegen 111 KB live -- auf vier Prozent genau),
   geurteilt wird ueber die uebertragene Groesse, berichtet werden
   beide. Dabei prompt in die dokumentierte Falle getappt: `body()` auf
   einen Ereignisstrom endet nie, der Lauf blieb stehen. Jetzt
   ausgenommen, plus Notbremse je Koerper.

2. pruef-ueberlappung konnte seit Tagen nicht mehr zeigen, dass sie
   eine Ueberlappung ueberhaupt findet: Ihre Gegenprobe legt einen Knopf
   auf einen anderen, traf aber 6 px daneben. Zwei Ursachen in einer
   Zeile -- `position: fixed` bezieht sich auf einen Vorfahren mit
   `transform`, und die `transition` der Knoepfe liefert beim sofortigen
   Messen die Lage von vorher. Jetzt `transform` + `transition: none`:
   34x36 px erkannt, 20 Seiten-Breiten-Paare, 0 Befunde.

PRUEFUNGEN: pruef-erklaerung neu mit 44, davon 8 im Browser (Seite mit
und ohne Kopfzeile, Handy, Kontrast, Echo). Mein Schild stand auf
10,88 px und lag unter der Hausgrenze von 11,5 -- gefunden von
pruef-css-klassen, nicht vom Auge.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 18:20:30 +02:00

178 lines
8.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Liegt auf einer Seite irgendein Bedienelement UEBER einem anderen?
Am 06.09.2026 lag der Sicht-Umschalter bei 412 px quer ueber dem
Chat-Knopf -- auf zwoelf Seiten. Wer auf "Meine Sicht" tippte, landete
im Chat. Gefunden wurde das durch Hinsehen; das hier misst es. */
import { chromium } from "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs";
import { spawn } from "node:child_process";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { scryptSync, randomBytes } from "node:crypto";
const ordner = mkdtempSync(join(tmpdir(), "ws-kopf-"));
const DB = join(ordner, "w.db");
const PORT = 4367, BASIS = `http://127.0.0.1:${PORT}`;
const start = () => spawn(process.execPath, ["server/index.js"], {
cwd: "C:/Users/qciga/Documents/Obelix/DogiHompage",
env: { ...process.env, WORKSPACE_DB: DB, PORT: String(PORT),
SITE_ACCESS_SECRET: "lokaler-test", SITE_PUBLIC_LAUNCH_AT: "2020-01-01T00:00:00+01:00" },
stdio: ["ignore", "pipe", "pipe"] });
const warten = async () => {
for (let i = 0; i < 80; i++) { await new Promise((r) => setTimeout(r, 250));
try { const a = await fetch(BASIS + "/workspace/", { redirect: "manual" }); if (a.status < 500) return; } catch { /* noch nicht */ } }
};
let kind = start(); await warten();
kind.kill(); await new Promise((r) => setTimeout(r, 700));
{
const d = new DatabaseSync(DB);
const s = randomBytes(16).toString("hex");
const h = scryptSync("CODE-DOGI-0001", s, 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 ('Filipe','admin',?,?,?,1,?)")
.run(h, s, 32768, new Date().toISOString());
d.close();
}
kind = start(); await warten();
const a = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }) });
const kekse = (a.headers.getSetCookie?.() || []).map((z) => {
const [np] = z.split(";"); const [name, ...wert] = np.split("=");
return { name: name.trim(), value: wert.join("="), domain: "127.0.0.1", path: "/" };
});
const SEITEN = ["start.html", "kalender.html", "aufgaben.html", "calls.html", "chat.html"];
/* Die Breiten, an denen es wirklich klemmt: 320 = kleinstes uebliches
Geraet, 412 = haeufigste Android-Breite, 768 = Tablet hochkant,
1280 = kleinster Laptop. Keine davon ist geraten. */
const BREITEN = [320, 412, 768, 1280];
const b = await chromium.launch();
let funde = 0, geprueft = 0;
for (const breite of BREITEN) {
const sitz = await b.newContext({ viewport: { width: breite, height: 900 } });
await sitz.addCookies(kekse);
const p = await sitz.newPage();
for (const seite of SEITEN) {
await p.goto(`${BASIS}/workspace/${seite}`, { waitUntil: "networkidle" }).catch(() => {});
await p.waitForTimeout(900);
const treffer = await p.evaluate(() => {
const nimm = (n) => { const r = n.getBoundingClientRect(); return { r, n }; };
/* Nur die Kopfleiste: dort stehen die Elemente am dichtesten, und
dort war der echte Fehler. */
const leiste = document.querySelector(".kopfleiste");
if (!leiste) return ["KEINE KOPFLEISTE"];
const teile = [...leiste.querySelectorAll("button, a, select, .knopf")]
.filter((n) => { const r = n.getBoundingClientRect(); return r.width > 0 && r.height > 0; })
.map(nimm);
const raus = [];
for (let i = 0; i < teile.length; i++) {
for (let j = i + 1; j < teile.length; j++) {
const A = teile[i], B = teile[j];
if (A.n.contains(B.n) || B.n.contains(A.n)) continue; // verschachtelt ist kein Fehler
/* DASSELBE BEDIENELEMENT ZAEHLT NICHT (Befund beim Bauen).
Der erste Lauf meldete zehnmal "Meine Sicht × Meine Sicht".
Das ist ein durchsichtiges <select>, das ueber seinem
eigenen gestalteten Schild liegt -- die uebliche Bauart fuer
eine Auswahl, die aussehen soll wie ein Knopf. Wer darauf
tippt, landet genau dort, wo er hinwollte.
Die Frage lautet nicht "ueberlappen zwei Kaesten", sondern
"komme ich woanders hin als ich tippe". Ueberlappungen
INNERHALB eines Bedienelements sind deshalb kein Fund --
sonst meldet die Pruefung jedes Mal dasselbe, und eine
Warnung, die immer kommt, ist keine Warnung mehr. */
const huelleA = A.n.closest(".sicht, .knopf, label, .wer");
const huelleB = B.n.closest(".sicht, .knopf, label, .wer");
if (huelleA && huelleA === huelleB) continue;
const x = Math.min(A.r.right, B.r.right) - Math.max(A.r.left, B.r.left);
const y = Math.min(A.r.bottom, B.r.bottom) - Math.max(A.r.top, B.r.top);
if (x > 2 && y > 2) {
const kurz = (n) => (n.textContent || n.className || n.tagName).trim().slice(0, 22);
raus.push(`${kurz(A.n)} × ${kurz(B.n)} (${Math.round(x)}×${Math.round(y)} px)`);
}
}
}
return raus;
});
geprueft++;
if (treffer.length) {
funde += treffer.length;
console.log(` FEHL ${seite} @${breite}px:`);
for (const t of treffer) console.log(" " + t);
}
}
await sitz.close();
}
/* =====================================================================
DIE GEGENPROBE -- ohne sie beweist "0 Ueberlappungen" gar nichts.
Eine Pruefung, die immer bestaetigt, bestaetigt nichts. Hier wird
deshalb absichtlich ein Knopf verschoben, bis er ueber einem anderen
liegt. Schlaegt die Pruefung dann NICHT an, ist sie kaputt -- und der
ganze Lauf muss scheitern, nicht nur eine Zeile melden.
Warum ausgerechnet dieser Fehler: Am 06.09.2026 lag der
Sicht-Umschalter bei 412 px quer ueber dem Chat-Knopf, auf zwoelf
Seiten. Wer "Meine Sicht" antippte, landete im Chat. Aufgefallen ist
das durch Hinsehen -- gemessen hatte es niemand. */
{
const sitz = await b.newContext({ viewport: { width: 412, height: 900 } });
await sitz.addCookies(kekse);
const p = await sitz.newPage();
await p.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" });
await p.waitForTimeout(900);
const merkt = await p.evaluate(() => {
const leiste = document.querySelector(".kopfleiste");
const teile = [...leiste.querySelectorAll("button, a, .knopf")]
.filter((n) => { const r = n.getBoundingClientRect(); return r.width > 0 && r.height > 0; });
if (teile.length < 2) return "zu wenige Bedienelemente zum Pruefen";
/* Den letzten Knopf genau auf den ersten legen. */
const erst = teile[0].getBoundingClientRect();
const letzt = teile[teile.length - 1];
/* MIT transform STATT position:fixed (14.09.2026).
`position: fixed` bezieht sich NICHT immer auf das Fenster: Hat
ein Vorfahr `transform`, `filter` oder `backdrop-filter`, wird er
selbst zum Bezugsrahmen. Die Kopfleiste hat so einen. Der Knopf
landete dadurch 6 px neben dem Ziel -- gemessen {x:-6, y:36} --,
die Gegenprobe meldete „nicht erkannt“, und damit konnte diese
Pruefung tagelang nicht mehr zeigen, dass sie eine Ueberlappung
ueberhaupt findet. Eine Pruefung, die das nicht kann, bestaetigt
nichts.
`transform` verschiebt relativ zur EIGENEN aktuellen Lage. Wo der
Bezugsrahmen liegt, ist damit gleichgueltig. */
/* UND OHNE UEBERBLENDUNG. Die Knoepfe der Kopfleiste haben eine
`transition`; wer unmittelbar nach dem Setzen misst, bekommt die
Lage von VORHER zurueck -- die Bewegung hat noch nicht
stattgefunden. Das war die zweite Haelfte desselben Fehlers.
Derselbe Grund, aus dem in pruef-dabei-optik das Haekchen
„0 → 0“ meldete, bis dort die Ueberblendung abgewartet wurde. */
letzt.style.transition = "none";
const vorher = letzt.getBoundingClientRect();
letzt.style.transform =
`translate(${erst.left - vorher.left}px, ${erst.top - vorher.top}px)`;
letzt.style.zIndex = "99";
// Messen wie oben, aber nur dieses eine Paar.
const a2 = teile[0].getBoundingClientRect(), b2 = letzt.getBoundingClientRect();
const x = Math.min(a2.right, b2.right) - Math.max(a2.left, b2.left);
const y = Math.min(a2.bottom, b2.bottom) - Math.max(a2.top, b2.top);
return { x: Math.round(x), y: Math.round(y) };
});
const erkannt = typeof merkt === "object" && merkt.x > 2 && merkt.y > 2;
console.log(erkannt
? ` ok Gegenprobe: ein absichtlich verschobener Knopf wird als Ueberlappung erkannt (${merkt.x}×${merkt.y} px)`
: ` FEHL Gegenprobe: der verschobene Knopf wurde NICHT erkannt (${JSON.stringify(merkt)})`);
if (!erkannt) funde++;
await sitz.close();
}
console.log(`\n${geprueft} Seiten-Breiten-Paare geprueft, ${funde} Befunde.`);
console.log(funde === 0 ? "ALLES IN ORDNUNG" : `${funde} FEHLER`);
await b.close(); kind.kill();
process.exit(funde ? 1 : 0);