Files
dogfather-universe/server/pruef-wissen-formular.mjs
DogFatherGitandClaude Opus 5 5a9f1f467c Wissen und Dateien: Zwei Raster, die ihren Inhalt verloren haben
Filipe zum Bildschirmfoto der Wissen-Seite: "erstens sieht das richtig
scheisse aus". Zu sehen war die Kategorie-Auswahl quer ueber "STUFE",
"GERAET", "VEROEFFENTLICHT AM" und den Tag-Knoepfen. Text auf Text.

Zweimal dieselbe Ursache, zweimal ein Raster, das Kinder in eine Zelle
zwingt, die zu klein ist:

1. FORMULAR "NEUE ANLEITUNG" (aufgaben.css). Die Regel
   `grid-template-rows: 1fr 44px` gibt jeder Zelle eine feste
   Eingabezeile -- richtig und wichtig, damit alle Felder auf einer
   Linie sitzen. Die Zelle "Hauptkategorie" enthaelt aber keine
   Eingabe, sondern sechs Gruppen mit zwanzig Knoepfen. Gemessen: der
   Kasten 40 px hoch, sein Inhalt 442 -- 402 px liefen ueber alles
   darunter. Unter 560 px passierte das nicht, weil dort ohnehin
   `auto auto` gilt; deshalb sah es am Handy richtig aus.
   Es ist exakt die Falle, die eine Regel darueber schon einmal
   zugeschnappt ist ("EINE ZELLE, DIE AUFKLAPPT ..."). Dieselbe
   Antwort, diesmal fuer .katwahl.

2. DATEILISTE (dateien.css). .datei hat die Spalten `34px 1fr auto`.
   Die ersten vier Kinder sitzen richtig; alles danach wird automatisch
   platziert und landet in der ZEICHENSPALTE. Gemessen: "Fassung 2" in
   34 px Breite, 22 px herausragend; auf 390 px zusaetzlich die
   Metazeile ("1,9 MB" in zwei Zeilen) und "Sichtbar fuer" mit 49 px
   Ueberstand.

ZWEI NEUE PRUEFUNGEN, und die erste war erst selbst falsch:
pruef-wissen-formular.mjs verglich zunaechst den KASTEN der Auswahl mit
dem der Zelle und meldete gruen, waehrend das Bildschirmfoto die
Ueberlappung deutlich zeigte. Der Kasten ist ja brav 44 px hoch --
herausgelaufen ist sein INHALT. Jetzt wird scrollHeight gegen
clientHeight gemessen, und die Pruefung wird rot, bevor der Fix
dazukommt. pruef-dateien-liste.mjs legt bewusst zweimal dieselbe Datei
ab, damit "Fassung 2" und "nicht mehr aktuell" ueberhaupt entstehen.

Versionsstempel neu gesetzt (202610011426, 670 Verweise in 45 Dateien).
Ohne ihn liegt die Korrektur auf dem Server und kommt bei niemandem an:
Cache-Control steht auf immutable.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-01 14:27:44 +02:00

192 lines
9.0 KiB
JavaScript

/* WISSEN: DAS FORMULAR "NEUE ANLEITUNG" (01.10.2026)
Filipe zum Bildschirmfoto: "erstens sieht das richtig scheisse aus".
Zu sehen war: die Kategorie-Auswahl lag quer ueber "STUFE", "GERAET",
"VEROEFFENTLICHT AM" und den Tag-Knoepfen. Text auf Text.
URSACHE (gemessen): aufgaben.css zwingt JEDES direkte Kind von
.neu__raster in zwei Rasterzeilen -- `grid-template-rows: 1fr 44px`.
Das ist richtig und wichtig, damit alle Eingabefelder auf derselben
Linie sitzen. Die Zelle "Hauptkategorie" enthaelt aber keine 44 px
hohe Eingabe, sondern .katwahl: sechs Gruppen mit zwanzig Knoepfen.
Die passen nicht in 44 px und laufen ueber alles darunter.
Dieselbe Falle war schon einmal da und ist im CSS dokumentiert
("EINE ZELLE, DIE AUFKLAPPT, PASST NICHT IN EINE FESTE ZEILE") --
damals fuer die aufklappbare Personenliste. Diese Pruefung sorgt
dafuer, dass sie nicht ein drittes Mal zuschnappt.
GEMESSEN WIRD NICHT "sieht gut aus", sondern: Ueberlappen sich zwei
Felder des Rasters? Das ist eine Zahl, und sie ist eindeutig.
DREI AUSGAENGE: 0 in Ordnung / 1 Befunde / 2 konnte nicht arbeiten. */
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-wissenform-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
const { eigenerPort } = await import("./helfer-port.mjs");
const PORT = await eigenerPort(import.meta, "die Wissen-Formularpruefung");
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");
const { notbremse } = await import("./helfer-notbremse.mjs");
notbremse(420_000, "pruef-wissen-formular");
await new Promise((r) => setTimeout(r, 1200));
const BASIS = `http://127.0.0.1:${PORT}`;
let geprueft = 0, fehler = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
/* ---- eine Person, die ablegen darf ---------------------------------- */
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
for (let i = 0; i < 60; i++) {
try {
const p = new DatabaseSync(process.env.WORKSPACE_DB);
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
} catch { await new Promise((r) => setTimeout(r, 250)); }
}
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("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
d.close();
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch();
/* Die Breiten sind nicht geraten: 1165 ist die aus Filipes Bildschirmfoto,
1280 der haeufigste Notebook-Wert, 900 und 412 die beiden Umbruchstufen
des Rasters (860 und 560) von oben bzw. unten her. */
const BREITEN = [1440, 1280, 1165, 900, 412];
for (const breite of BREITEN) {
const ctx = await browser.newContext({ viewport: { width: breite, height: 1000 }, hasTouch: breite <= 860 });
const seite = await ctx.newPage();
const konsole = [];
seite.on("pageerror", (f) => konsole.push("Absturz: " + f.message));
seite.on("console", (m) => { if (m.type() === "error") konsole.push("Konsole: " + m.text()); });
await seite.goto(BASIS + "/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.waitForURL("**/start.html", { timeout: 25000 });
await seite.goto(BASIS + "/workspace/wissen.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(800);
console.log(`\n--- ${breite} px ---`);
const aufKnopf = seite.locator("#neu-auf");
if (!(await aufKnopf.count()) || !(await aufKnopf.isVisible())) {
console.log(" KONNTE NICHT ARBEITEN: Knopf \"PDF hinzufuegen\" nicht sichtbar.");
await ctx.close(); await browser.close(); process.exit(2);
}
await aufKnopf.click();
await seite.waitForTimeout(700);
/* Eine Kategorie waehlen: Erst dann erscheint der Beschreibungstext
unter der Auswahl -- und genau der lag auf Filipes Bild quer ueber
der naechsten Ueberschrift. Ohne Auswahl waere die Pruefung blind
fuer den schlimmeren der beiden Faelle. */
const ersteKat = seite.locator("#n-kategorie button").first();
if (await ersteKat.count()) { await ersteKat.click(); await seite.waitForTimeout(400); }
const befund = await seite.evaluate(() => {
const raster = document.querySelector("#neu-form .neu__raster");
if (!raster) return { fehlt: true };
const kinder = [...raster.children];
const kasten = (el) => { const r = el.getBoundingClientRect();
return { o: r.top, u: r.bottom, l: r.left, r: r.right, h: r.height }; };
/* 1. Ueberlappen sich zwei Felder? */
const ueber = [];
for (let i = 0; i < kinder.length; i++) {
for (let j = i + 1; j < kinder.length; j++) {
const a = kasten(kinder[i]), b = kasten(kinder[j]);
const sen = Math.min(a.u, b.u) - Math.max(a.o, b.o);
const waa = Math.min(a.r, b.r) - Math.max(a.l, b.l);
if (sen > 1 && waa > 1) {
ueber.push({ a: i + 1, b: j + 1, px: Math.round(sen),
at: (kinder[i].textContent || "").trim().slice(0, 28),
bt: (kinder[j].textContent || "").trim().slice(0, 28) });
}
}
}
/* 2. Laeuft der INHALT der Kategorie-Auswahl aus ihr heraus?
ERST FALSCH GEMESSEN (01.10.2026): Der erste Versuch verglich
den KASTEN der Auswahl mit dem der Zelle -- und meldete gruen,
waehrend das Bildschirmfoto die Ueberlappung deutlich zeigte.
Der Kasten ist naemlich brav 44 px hoch; herausgelaufen ist
sein INHALT. Eine Pruefung, die den Kasten misst, kann das
nie sehen. Gemessen wird deshalb scrollHeight gegen
clientHeight und zusaetzlich das unterste Kind. */
const wahl = raster.querySelector(".katwahl");
let raus = null, ueberHang = null, hoch = null;
if (wahl) {
ueberHang = Math.round(wahl.scrollHeight - wahl.clientHeight);
const w = wahl.getBoundingClientRect();
hoch = Math.round(w.height);
let tiefstes = w.bottom;
for (const kind of wahl.children) {
const k = kind.getBoundingClientRect();
if (k.bottom > tiefstes) tiefstes = k.bottom;
}
raus = Math.round(tiefstes - w.bottom);
}
return { fehlt: false, anzahl: kinder.length, ueber, raus, ueberHang, hoch };
});
if (befund.fehlt) {
console.log(" KONNTE NICHT ARBEITEN: Das Formularraster wurde nicht gefunden.");
await ctx.close(); await browser.close(); process.exit(2);
}
ok(befund.anzahl >= 6, `das Raster hat ${befund.anzahl} Felder`);
ok(befund.raus !== null, `die Kategorie-Auswahl ist da (${befund.hoch} px hoch)`);
ok(befund.ueberHang !== null && befund.ueberHang <= 1,
`der Inhalt passt in die Auswahl (${befund.ueberHang} px Ueberhang)`);
ok(befund.raus !== null && befund.raus <= 1,
`nichts ragt unten heraus (${befund.raus} px)`);
ok(befund.ueber.length === 0,
`kein Feld liegt auf einem anderen (${befund.ueber.length} Ueberlappungen)`);
for (const u of befund.ueber) {
console.log(` Feld ${u.a} ("${u.at}") liegt ${u.px} px auf Feld ${u.b} ("${u.bt}")`);
}
ok(konsole.length === 0, `keine Fehler in der Konsole${konsole.length ? " -> " + konsole.join(" | ") : ""}`);
/* Zum Hinsehen, wenn eine Zahl nicht reicht: SCHUSS=ja node pruef-wissen-formular.mjs */
if (process.env.SCHUSS === "ja") {
const { mkdirSync } = await import("node:fs");
mkdirSync(join(process.cwd(), "schuss-wissenform"), { recursive: true });
await seite.locator("#neu-block").screenshot({
path: join(process.cwd(), "schuss-wissenform", `form-${breite}.png`) });
console.log(` Bild: schuss-wissenform/form-${breite}.png`);
}
await ctx.close();
}
await browser.close();
console.log(`\n${geprueft} Pruefungen, ${fehler} Befunde`);
/* Die Zahl gehoert in die BEDINGUNG: Eine Pruefung, die nichts gemessen
hat, darf nicht gruen sein. */
if (geprueft < BREITEN.length * 6) {
console.log("ERGEBNIS: KONNTE NICHT ARBEITEN (zu wenige Messungen)");
process.exit(2);
}
console.log(fehler ? "ERGEBNIS: NICHT IN ORDNUNG" : "ERGEBNIS: IN ORDNUNG");
process.exit(fehler ? 1 : 0);