Das Aufgaben-Formular: was aufklappt, bekommt jetzt auch Platz

Filipe, 22.09.2026, zum Bildschirmfoto: „wie scheisse sieht das aus,
verbesser das bitte." Zu sehen: „Anlegen" und „Abbrechen" lagen mitten
in der Personenliste.

GEMESSEN statt geraten, bei 1280 px mit aufgeklappter Liste:

    Zelle            Zeilen          Hoehe   Inhalt
    feld-verant      24px 44px          68      107
    feld-mehrere     43px 44px          87      261   <-- 174 zu viel

Jede Zelle des Formularrasters bekommt zwei Zeilen: Beschriftung oben,
Eingabe unten mit festen 44 px. Das ist richtig und der Grund, warum
alle Eingaben auf einer Linie sitzen (17.09.).

`feld-mehrere` ist aber keine Beschriftung mit Eingabe, sondern ein
Knopf mit einer Liste, die aufklappt. Ihr zweites Kind landet in der
44-Pixel-Zeile und laeuft heraus, sobald jemand sie oeffnet. Was
herauslaeuft, belegt keinen Platz -- also legt es sich ueber das
Naechste, und das Naechste sind die Knoepfe.

DIE MESSUNG HAT MICH VOR DEM NAHELIEGENDEN FEHLER BEWAHRT.
Erster Gedanke: „Zellen ohne eigene Eingabe brauchen die zwei Zeilen
nicht" -- `:has(> input, > select, > textarea)`. Die Messung sagt etwas
anderes: Von sieben Zellen haben SECHS ihre Eingabe nicht als direktes
Kind, weil der Auswahl-Baustein sie in ein <div> wickelt. Die Regel
haette fast das ganze Formular getroffen und genau die Ausrichtung
zerstoert, die sie schuetzen soll.

`aria-expanded` ist gemessen das einzige Merkmal, das nur bei dieser
Zelle steht -- und es ist das inhaltlich richtige: Es sagt „dieser
Bereich kann groesser werden". Etwas, das groesser werden kann, darf
keine feste Hoehe haben.

UND DIE PRUEFUNG, DIE ES HAETTE FINDEN MUESSEN
pruef-formulare war gruen -- sie klappt das Feld nie auf. Ein Zustand,
der nie hergestellt wird, kann nicht gemessen werden.

Neuer Abschnitt: Jedes Element mit `aria-expanded` im Formular wird
geoeffnet, danach darf keine Rasterzelle mehr Inhalt haben, als sie
hoch ist. Nicht diese eine Stelle, sondern die Eigenschaft -- damit
faellt auch die naechste auf, die es noch gar nicht gibt.

ZWEI ANLAEUFE DABEI WAREN FALSCH, und der zweite war der gefaehrliche:
  1. `scrollHeight` der Zelle meldete elf Zellen als kaputt, die alle
     in Ordnung sind: Der Hinweis unter einem Feld haengt seit dem
     17.09. absichtlich absolut darunter. Eine Warnung, die bei
     richtigem Verhalten anschlaegt, wird abgeschaltet.
  2. Nur die direkten Kinder im Fluss -- das war gruen, AUCH MIT DEM
     ECHTEN FEHLER. Nachgemessen mit zurueckgenommener Behebung:
     immer noch gruen. Das Raster staucht das Kind auf die feste
     Zeilenhoehe, das Kind bleibt also brav in der Zelle; was
     herauslaeuft, ist der Inhalt darin.

Jetzt misst sie in die Tiefe (ohne Teilbaeume unter absolut gesetzten
Elementen und ohne eigene Rollbereiche) und ist beidseitig belegt:
  mit Behebung    -> gruen
  ohne Behebung   -> FEHL „feld-mehrere: Inhalt reicht bis 169 px,
                     Zelle ist 87 px hoch"
Dazu eine Gegenprobe, die eine Zelle kuenstlich einklemmt.

Ueberlappungen im Formular: von 12 auf 4 -- und die vier sind Absicht
(der echte <select> liegt unsichtbar ueber seinem Knopf).

pruef-formulare, pruef-aufgabenbrett, pruef-css-klassen gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-22 14:58:45 +02:00
co-authored by Claude Opus 5
parent beb6bc2d62
commit 7ccf904c3c
41 changed files with 792 additions and 577 deletions
+175
View File
@@ -310,6 +310,181 @@ console.log("\n=== Kein Feld ohne Gestaltung");
await kontext.close();
}
/* =====================================================================
WAS AUFKLAPPT, MUSS AUCH PLATZ BEKOMMEN (22.09.2026)
=====================================================================
Filipe zum Bildschirmfoto: „wie scheisse sieht das aus, verbesser das
bitte." Zu sehen war das Aufgaben-Formular: „Anlegen" und
„Abbrechen" lagen mitten in der Personenliste.
WARUM DIE PRUEFUNG DARUEBER DAS NICHT FINDEN KONNTE: Sie oeffnet das
Formular und misst die Felder -- im zugeklappten Zustand. Der Fehler
entsteht erst beim AUFKLAPPEN. Ein Zustand, der nie hergestellt
wird, kann nicht gemessen werden; die Pruefung war gruen und hat
dabei nie hingesehen.
Gemessen war es dann eindeutig, bei 1280 px:
Zelle Zeilen Hoehe Inhalt
feld-verant 24px 44px 68 107
feld-mehrere 43px 44px 87 261 <-- 174 zu viel
Die Zelle steckte in einer festen 44-Pixel-Zeile. Was herauslaeuft,
belegt keinen Platz -- also legt es sich ueber das Naechste.
HIER WIRD NICHT DIESE EINE STELLE GEPRUEFT, sondern die Eigenschaft:
Jedes Element mit `aria-expanded` im Formular wird aufgeklappt, und
danach darf keine Rasterzelle mehr Inhalt haben, als sie hoch ist.
Damit faellt auch die naechste auf, die es noch gar nicht gibt.
===================================================================== */
console.log("\n=== Was aufklappt, bekommt auch Platz");
{
const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } });
const seite = await kontext.newPage();
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.waitForTimeout(900);
let zellenGesamt = 0, aufgeklapptGesamt = 0;
const schlimm = [];
for (const [wohin, knopf] of FORMULARE) {
await seite.goto(`${BASIS}/workspace/${wohin}`, { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
const auf = await seite.$(knopf);
if (!auf) continue;
await auf.click().catch(() => {});
await seite.waitForTimeout(700);
/* ALLES AUFKLAPPEN, was sich aufklappen laesst. */
const wieViele = await seite.evaluate(() => {
const f = document.querySelector(".neu__raster");
if (!f) return 0;
const knoepfe = [...f.querySelectorAll("[aria-expanded]")]
.filter((b) => b.offsetParent !== null);
for (const b of knoepfe) b.click();
return knoepfe.length;
});
await seite.waitForTimeout(600);
aufgeklapptGesamt += wieViele;
const befund = await seite.evaluate(() => {
const r = document.querySelector(".neu__raster");
if (!r) return { zellen: 0, zuEng: [] };
const zellen = [...r.children].filter((c) => !c.hidden && c.offsetParent !== null);
/* WIE TIEF REICHT DER INHALT WIRKLICH -- zwei Anlaeufe davor
waren falsch, und der zweite war der gefaehrlichere:
(1) `scrollHeight` der Zelle. Meldete elf Zellen als kaputt,
die alle in Ordnung sind: Der Hinweis unter einem Feld
haengt seit dem 17.09. ABSICHTLICH absolut unter der Zelle
(`top: 100%`). Er ragt hinaus, das ist seine Aufgabe. Eine
Warnung, die bei richtigem Verhalten anschlaegt, wird
abgeschaltet.
(2) Nur die DIREKTEN Kinder im Fluss. Das war gruen -- auch
mit dem echten Fehler. Nachgemessen mit
zurueckgenommener Behebung: immer noch gruen. Der Grund
ist das Raster selbst: Es staucht das Kind auf die feste
Zeilenhoehe, das Kind bleibt also brav in der Zelle. Was
herauslaeuft, ist der Inhalt DARIN. Eine Pruefung, die
nur eine Ebene tief sieht, sieht davon nichts.
Also in die Tiefe -- aber nicht blind: Teilbaeume unter
absolut gesetzten Elementen bleiben aussen vor (siehe 1), und
wer selbst scrollt (`overflow` nicht `visible`), darf Inhalt
unterhalb haben; das ist der Sinn eines Rollbereichs. */
const unten = (c) => {
const oben = c.getBoundingClientRect().top;
let tiefste = 0;
const geh = (el) => {
for (const k of el.children) {
const st = getComputedStyle(k);
if (st.position === "absolute" || st.position === "fixed") continue;
if (st.display === "none" || k.hidden) continue;
tiefste = Math.max(tiefste, k.getBoundingClientRect().bottom - oben);
if (st.overflow === "visible" && st.overflowY === "visible") geh(k);
}
};
geh(c);
return Math.round(tiefste);
};
const zuEng = zellen
/* 4 px Luft fuer Abrundungen im Rastermass. 174 px faellt
hier nicht durch. */
.filter((c) => unten(c) > Math.round(c.getBoundingClientRect().height) + 4)
.map((c) => `${c.id || "(ohne Kennung)"}: Inhalt reicht bis ${unten(c)} px, `
+ `Zelle ist ${Math.round(c.getBoundingClientRect().height)} px hoch`);
return { zellen: zellen.length, zuEng };
});
zellenGesamt += befund.zellen;
for (const z of befund.zuEng) schlimm.push(`${wohin} — ${z}`);
}
/* DIE ANZAHL GEHOERT IN DIE BEDINGUNG. Ohne sie waere „nichts laeuft
heraus" auch dann gruen, wenn gar nichts aufgeklappt wurde -- und
genau das war der Fehler der Pruefung darueber. */
ok(zellenGesamt > 10, `${zellenGesamt} Rasterzellen angesehen`);
ok(aufgeklapptGesamt > 0,
`${aufgeklapptGesamt} aufklappbare Bereiche wirklich geoeffnet`);
ok(schlimm.length === 0 && aufgeklapptGesamt > 0,
schlimm.length
? `${schlimm.length} Zelle(n) laufen aus ihrer Zeile heraus:\n `
+ schlimm.join("\n ")
: "keine Zelle laeuft aus ihrer Zeile heraus");
/* GEGENPROBE: Die Messung muss ein Herauslaufen auch SEHEN. Dafuer
wird eine Zelle kuenstlich eingeklemmt -- und danach wieder
freigegeben. Ohne diese Zeile waere „nichts laeuft heraus" die
Aussage einer Messung, von der niemand weiss, ob sie etwas
bemerken wuerde. */
/* Fuer die Gegenprobe zurueck auf ein Formular, das offen ist -- der
letzte Durchlauf oben kann auf einer Seite geendet haben, die gar
keines hat. Eine Gegenprobe, die mangels Gelegenheit ausfaellt,
ist keine. */
await seite.goto(`${BASIS}/workspace/aufgaben.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
await seite.click("#neu-oeffnen").catch(() => {});
await seite.waitForTimeout(700);
const probe = await seite.evaluate(() => {
/* Dieselbe Rechnung wie oben -- sonst prueft die Gegenprobe eine
andere Messung als die, um die es geht. */
const unten = (c) => {
const oben = c.getBoundingClientRect().top;
let tiefste = 0;
const geh = (el) => {
for (const k of el.children) {
const st = getComputedStyle(k);
if (st.position === "absolute" || st.position === "fixed") continue;
if (st.display === "none" || k.hidden) continue;
tiefste = Math.max(tiefste, k.getBoundingClientRect().bottom - oben);
if (st.overflow === "visible" && st.overflowY === "visible") geh(k);
}
};
geh(c);
return Math.round(tiefste);
};
const r = document.querySelector(".neu__raster");
const c = r && [...r.children].find((x) => !x.hidden && x.offsetParent !== null && unten(x) > 40);
if (!c) return null;
const vorher = c.style.height;
c.style.height = "12px";
const erkannt = unten(c) > Math.round(c.getBoundingClientRect().height) + 4;
c.style.height = vorher;
const wiederGut = unten(c) <= Math.round(c.getBoundingClientRect().height) + 4;
return { erkannt, wiederGut };
});
ok(!!probe && probe.erkannt && probe.wiederGut,
probe ? "Gegenprobe: eine kuenstlich eingeklemmte Zelle wird erkannt"
: "KONNTE NICHT NACHSEHEN: keine Zelle fuer die Gegenprobe gefunden");
await kontext.close();
}
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(fehler ? `\n${fehler} Formular(e) fehlerhaft.` : "\nAlles in Ordnung.");