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]>
178 lines
8.9 KiB
JavaScript
178 lines
8.9 KiB
JavaScript
/* 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);
|