Handy durchgeprueft, Lichtfarbe je Kachel, haengendes Licht behoben

DAS LICHT BLIEB HAENGEN -- vier Ursachen, jede einzeln behoben:

1. EIN WETTLAUF. Zwischen dem Anmelden eines Bildaufbaus und seinem
   Ablauf konnte der Zeiger die Karte laengst verlassen haben. Dann hatte
   pointerout schon aufgeraeumt, und der Bildaufbau schaltete das Licht
   gleich wieder AN. Der haeufigste Fall und der unauffaelligste.
2. UEBER EINE LUECKE VERLASSEN. Wer eine Karte nicht ueber eine
   Nachbarkarte verliess, sondern ueber den Zwischenraum, loeste kein
   Ereignis aus, das die alte Karte kannte.
3. GESCROLLT, OHNE DIE MAUS ZU BEWEGEN. Die Karte wandert unter dem
   stehenden Zeiger weg -- es kommt gar kein Zeigerereignis. Jetzt wird
   beim Scrollen nachgesehen, ob die beleuchtete Karte noch unter dem
   Zeiger liegt.
4. FENSTER ODER TAB VERLASSEN. Auch dort kommt nichts mehr.

Es gibt jetzt genau EINE beleuchtete Karte -- mehr kann es nicht geben,
es gibt ja nur einen Zeiger. Beim Wechsel geht die alte aus, bevor die
neue angeht.

DIE FARBE GEHOERT ZUR KACHEL. Auf der Wissensseite leuchteten alle sechs
Welten violett, weil die Farbe dort --w heisst und der ganze uebrige
Workspace --ton benutzt. Das Licht griff auf --ton zu, fand nichts und
nahm den Farbton der SEITE. Dieselbe Luecke bei den Aufgaben-Spalten
(--sfarbe), den Kalenderkarten (--afarbe) und den Kalenderzeilen
(--zfarbe). Alle vier setzen jetzt --ton mit.

Nachgewiesen an echten Bildpunkten, nicht am Quelltext: Der Zeiger wird
auf die Aufgaben-Kachel gesetzt (dort muss Rot ueberwiegen: 85 zu 22)
und auf die Kalender-Kachel (dort Blau: 71 zu 13). Ein
Zeichenkettenvergleich haette das nicht gekonnt -- der Browser rechnet
color-mix() aus und schreibt je nach Fassung rgb(), color() oder oklab()
zurueck.

DAS HANDY, komplett durchgeprueft: neue pruef-handy.mjs faehrt alle
fuenfzehn Seiten auf DREI echten Geraetegroessen ab (320 px iPhone SE,
390 px iPhone, 412 px Android) und misst fuenf Dinge, die am Rechner
unsichtbar sind. Gefunden und behoben:

  * ZWEI ECHTE UEBERLAEUFE (startcheck +27 px, automation +33 px). Die
    Seite liess sich waagerecht schieben -- auf einem Telefon der
    schlimmste Fehler. Ursachen: zwoelf Raster mit festem Mindestmass
    (minmax(280px, 1fr) kann nicht schrumpfen -> min(280px, 100%)),
    zwoelf feste Mindestbreiten an Auswahlfeldern, und ein "flex: none"
    an den Stufen-Knoepfen, das jedes Schrumpfen verbot. "width: 100%"
    allein reichte dort nicht.
  * BERUEHRZIELE unter 24 px (WCAG 2.2, Kriterium 2.5.8). Auswahlfelder
    waren 18 bis 22 px hoch. Jetzt 44 px -- die Empfehlung von Apple und
    Google, und der Daumen ist nun einmal breiter als ein Mauszeiger.
    Nebenbei behebt die Schriftgroesse 16 px das Hineinzoomen von iOS.
  * SCHRIFT unter 11,7 px an 52 Stellen. Gesucht wurden sie nicht von
    Hand -- das waere ein Ratespiel gewesen und haette die Haelfte
    uebersehen -- sondern durch Durchsuchen der Stildateien nach
    font-size unter 0,73rem.

ZWEI FEHLER IN DEN EIGENEN PRUEFUNGEN gefunden und behoben: Die
Schriftregel griff zuerst gar nicht (start.css wird VOR den Seitenstilen
geladen, bei gleicher Staerke gewinnt die spaetere -- jetzt mit
body.start qualifiziert), und die Lichtpruefung scrollte 600 px und
stellte nicht zurueck, sodass die folgende Pruefung ins Leere zeigte.

GEGENPROBE gemacht: Mit absichtlich eingebauten Fehlern (8-px-Schrift,
500 px breiter Inhalt) meldet die Handypruefung sofort Rot. Eine
Pruefung, die immer bestaetigt, bestaetigt nichts.

Alle achtzehn Pruefungen laufen gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-01 14:48:48 +02:00
co-authored by Claude Opus 5
parent 41ffb91688
commit 1fe443e59c
48 changed files with 834 additions and 149 deletions
+123
View File
@@ -449,6 +449,129 @@ console.log("\n=== Das Licht folgt dem Zeiger");
await seite.mouse.move(5, 5);
await seite.waitForTimeout(400);
}
/* --- Bleibt das Licht haengen? -----------------------------------------
Beanstandet am 01.09.2026: "teilweise bleibt das Licht haengen".
Es gab dafuer vier Wege, und jeder einzelne wird hier durchgespielt.
Alle vier haben dasselbe Merkmal: Der Zeiger ist weg, aber die Karte
leuchtet weiter. */
{
const anzahlAn = () => seite.evaluate(() =>
document.querySelectorAll('[data-licht="an"]').length);
/* 1. Ueber die Luecke zwischen zwei Kacheln hinaus. */
const k1 = await seite.locator(".kachel").nth(1).boundingBox();
await seite.mouse.move(k1.x + k1.width / 2, k1.y + k1.height / 2);
await seite.waitForTimeout(300);
ok(await anzahlAn() === 1, `beim Ueberfahren leuchtet genau eine Kachel (${await anzahlAn()})`);
await seite.mouse.move(k1.x + k1.width / 2, k1.y - 30); // in die Luecke darueber
await seite.waitForTimeout(350);
ok(await anzahlAn() === 0, `in der Luecke geht es aus (${await anzahlAn()})`);
/* 2. Direkt von einer Kachel auf die naechste -- es darf nie mehr
als eine gleichzeitig leuchten. */
const k2 = await seite.locator(".kachel").nth(2).boundingBox();
await seite.mouse.move(k1.x + k1.width / 2, k1.y + k1.height / 2);
await seite.waitForTimeout(250);
await seite.mouse.move(k2.x + k2.width / 2, k2.y + k2.height / 2);
await seite.waitForTimeout(300);
ok(await anzahlAn() === 1, `beim Wechsel leuchtet weiterhin nur eine (${await anzahlAn()})`);
/* 3. Gescrollt, ohne die Maus zu bewegen. Die Karte wandert unter
dem stehenden Zeiger weg -- es kommt KEIN Zeigerereignis. Das war
der haeufigste Fall. */
await seite.mouse.wheel(0, 600);
await seite.waitForTimeout(450);
ok(await anzahlAn() === 0, `nach dem Scrollen ist es aus (${await anzahlAn()})`);
/* WIEDER HOCH. Ohne das lagen die Kacheln fuer alle folgenden
Pruefungen 600 px weiter oben -- die naechste Pruefung bewegte den
Zeiger dann ins Leere und meldete einen Fehler, den es nicht gab. */
await seite.mouse.wheel(0, -600);
await seite.waitForTimeout(400);
/* 4. Der Wettlauf: schnell hinein und sofort wieder hinaus. Der
angemeldete Bildaufbau lief frueher NACH dem Verlassen und
schaltete das Licht wieder an -- so blieb es haengen. */
await seite.mouse.move(0, 0);
await seite.waitForTimeout(200);
const k3 = await seite.locator(".kachel").nth(0).boundingBox();
if (k3) {
await seite.mouse.move(k3.x + 20, k3.y + 20);
await seite.mouse.move(k3.x - 60, k3.y - 60); // im selben Wimpernschlag wieder raus
await seite.waitForTimeout(500);
ok(await anzahlAn() === 0, `hinein und sofort wieder hinaus: aus (${await anzahlAn()})`);
}
}
/* --- Die Farbe des Lichts gehoert zur Kachel ---------------------------
Beanstandet: Es leuchtete in der Farbe der SEITE statt in der der
Kachel. Geprueft wird, dass zwei verschieden gefaerbte Kacheln auch
verschieden gefaerbtes Licht bekommen. */
{
const farben = [];
for (const nr of [1, 2, 3]) {
const b = await seite.locator(".kachel").nth(nr).boundingBox();
if (!b) continue;
await seite.mouse.move(b.x + b.width / 2, b.y + b.height / 2);
await seite.waitForTimeout(300);
farben.push(await seite.evaluate((n) => {
const k = document.querySelectorAll(".kachel")[n];
const l = k.querySelector(".licht");
return {
ton: getComputedStyle(k).getPropertyValue("--ton").trim(),
licht: l ? getComputedStyle(l, "::before").backgroundImage.slice(0, 90) : null,
};
}, nr));
}
const toene = new Set(farben.map((f) => f.ton));
ok(toene.size === farben.length,
`jede Kachel hat ihren eigenen Ton (${[...toene].join(", ")})`);
/* Die Farbe im Verlauf NICHT als Text vergleichen: Der Browser
rechnet color-mix() aus und schreibt das Ergebnis je nach Fassung
als rgb(), color(srgb ...) oder oklab() zurueck. Ein
Zeichenkettenvergleich schlaegt dann fehl, obwohl die Farbe
stimmt -- genau das ist beim ersten Lauf passiert.
Verglichen werden stattdessen die Lichter UNTEREINANDER: Drei
verschieden gefaerbte Kacheln muessen drei verschiedene Verlaeufe
ergeben. Kaeme die Farbe weiterhin von der SEITE (der Fehler, den
Filipe gesehen hat), waeren alle drei gleich. */
const lichter = new Set(farben.map((f) => f.licht));
ok(lichter.size === farben.length,
`und das Licht ist je Kachel ein anderes (${lichter.size} von ${farben.length})`);
/* Zusaetzlich an echten Bildpunkten: Das Licht der Aufgaben-Kachel
(orange) muss mehr Rot als Blau haben, das der Kalender-Kachel
(tuerkis) mehr Blau als Rot. Das kann keine Zeichenkette
vortaeuschen. */
for (const [name, pruefung] of [["Aufgaben", (p) => p[0] > p[2]],
["Kalender", (p) => p[2] > p[0]]]) {
const b = await seite.evaluate((n) => {
const k = [...document.querySelectorAll(".kachel")]
.find((x) => x.querySelector(".kachel__name")?.textContent === n);
if (!k) return null;
const r = k.getBoundingClientRect();
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
}, name);
if (!b) continue;
await seite.mouse.move(b.x, b.y);
await seite.waitForTimeout(350);
const bild = await seite.screenshot({ type: "png" });
const p = await seite.evaluate(async ({ b64, x, y }) => {
const i = new Image(); i.src = "data:image/png;base64," + b64; await i.decode();
const c = document.createElement("canvas");
c.width = i.width; c.height = i.height;
const g = c.getContext("2d"); g.drawImage(i, 0, 0);
const s = i.width / window.innerWidth;
return [...g.getImageData(Math.round(x * s), Math.round(y * s), 1, 1).data];
}, { b64: bild.toString("base64"), x: b.x, y: b.y });
ok(pruefung(p), `das Licht auf "${name}" hat die richtige Farbe `
+ `(rot ${p[0]}, grün ${p[1]}, blau ${p[2]})`);
}
await seite.mouse.move(2, 2);
await seite.waitForTimeout(300);
}
ok(ruhe === "(Ruhelage)", `ruhend keine eigene Position gesetzt (${ruhe})`);
await seite.mouse.move(kasten.x + kasten.width * 0.2, kasten.y + kasten.height * 0.3);