Checklisten-Gruppen klappen zu, Markierungen tragen ein Zeichen

Zwei Wuensche vom 01.09.2026:
  "neben den Titel soll immer ein Knopf sein, wo man die Liste aufmacht
   oder wieder zumacht -- die sollen auch zu, damit die Seiten nicht so
   lang sind."
  "und wenn die Ansprechpartner was markieren, sollen die da so ein
   Zeichen haben, so dass die sehen, da ist was -- und es soll auch oben
   in der Kachel angezeigt werden."

ZUKLAPPEN. Der Gruppenkopf ist jetzt ein Knopf ueber die volle Breite
(49 px hoch, mit dem Daumen treffbar), nicht ein Pfeilchen daneben. Zu
ist der Standard. Gemessen: Die LIVE-Analyse ist damit 1100 statt 2081
Pixel lang -- 47 Prozent gespart. Welche Gruppen offen waren, merkt sich
der Browser; sonst waere jede Bewertung ein Ruecksprung an den Anfang.

DAS ZEICHEN. Eine Raute mit Ausrufestrich, in Bernstein. Drei
Entscheidungen, keine davon Geschmack:
  * FORM -- alles andere auf dem Bildschirm ist rund oder eckig. Eine
    Spitze nach oben gibt es sonst nirgends, und deshalb findet das Auge
    sie zwischen zwanzig Kacheln ohne Suchen.
  * FARBE -- immer dieselbe, nie die der Kachel. Ein Zeichen, das die
    Farbe wechselt, muss gelesen werden; eines, das immer gleich
    aussieht, wird erkannt. Rot waere falsch: "verbessern" ist ein
    Auftrag, kein Fehler.
  * BEWEGUNG -- ein Atmen ueber 3,2 s, kein Blinken; bei "weniger
    Bewegung" bleibt der Schein stehen statt zu verschwinden.

Es steht an drei Stellen, immer aus derselben Quelle (bereiche.js): am
Gruppenkopf, am Punkt selbst und oben auf der Kachel der Startseite.

WARUM DAS ZEICHEN AM GRUPPENKOPF PFLICHT IST. Ohne es waere Zuklappen
ein Rueckschritt gewesen: Der Betreuer markiert etwas, die Gruppe ist zu,
und der Creator erfaehrt es nie. Die Zahl "zu verbessern" in der Bilanz
klappt die betroffenen Gruppen jetzt auf und springt hin.

Auf der Kachel nur fuer Creator. Fuer einen Betreuer waere es die Liste
dessen, was er selbst angehakt hat -- sie waechst mit seiner Arbeit, und
nur der Creator kann sie abbauen. Ein Zaehler, den man nicht auf null
bringen kann, wird ignoriert, und dann sind auch die daneben nichts wert.

ZWEI FEHLER, DIE DIE PRUEFUNG GEFUNDEN HAT:
  1. Das Zeichen stiess auf der Kachel mit der Zahl zusammen (zwei
     Pixel). Behoben an der Ursache: Besteht die Zahl NUR aus
     Markierungen, ist sie dieselbe Auskunft ein zweites Mal und
     entfaellt; sonst ruecken Zahl und Pfeil nach unten.
  2. Danach war die Pruefung wertlos -- sie verglich mit einem Element,
     das es nun nicht mehr gab, und war ohne einen einzigen Vergleich
     gruen. Sie zaehlt jetzt die geprueften Nachbarn mit; null Nachbarn
     ist ein Fehler, kein Erfolg.

Geprueft wird SICHTBARKEIT, nicht Vorhandensein: Ein zugeklappter Punkt
steht weiterhin im Dokument, alle bisherigen Pruefungen waeren gruen
geblieben, auch wenn der Creator seine Markierung nie zu Gesicht
bekaeme. Dazu Gegenproben: keine Markierung ohne Grund, und wer nichts
markiert bekommen hat, sieht auch kein Zeichen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-01 19:42:34 +02:00
co-authored by Claude Opus 5
parent f9bf7d5674
commit d4cd8952b7
35 changed files with 740 additions and 151 deletions
+143
View File
@@ -315,6 +315,149 @@ for (const [rolle, code, name, darf] of [
ok(/Verbessern/i.test(s?.marke || ""), ` mit lesbarer Marke ("${s?.marke}")`);
ok(/Mikro/.test(s?.grund || ""), ` und dem Grund ("${s?.grund}")`);
await seite.screenshot({ path: "pruef-checkliste.png", fullPage: false });
/* =================================================================
ZUGEKLAPPTE GRUPPEN (Wunsch 01.09.2026)
Die gefaehrlichste Stelle des ganzen Umbaus. Alle Pruefungen oben
zaehlen ELEMENTE -- und ein zugeklappter Punkt ist im Dokument
weiterhin vorhanden. Sie waeren also alle gruen geblieben, auch
wenn der Creator seine Markierung nie zu Gesicht bekaeme.
Gemessen wird deshalb SICHTBARKEIT, nicht Vorhandensein.
================================================================= */
const zu = await seite.evaluate(() => {
const gruppen = [...document.querySelectorAll(".fest-gruppe")];
const sichtbar = (e) => {
if (!e) return false;
const r = e.getBoundingClientRect();
return r.width > 0 && r.height > 0;
};
return {
gruppen: gruppen.length,
zuGeklappt: gruppen.filter((g) => g.querySelector(".fest-gruppe__koerper").hidden).length,
ariaZu: gruppen.filter((g) =>
g.querySelector(".fest-gruppe__kopf").getAttribute("aria-expanded") === "false").length,
/* Kein einziger Punkt darf sichtbar sein, solange alles zu ist. */
sichtbarePunkte: [...document.querySelectorAll(".fest-punkt")].filter(sichtbar).length,
/* Das Merkzeichen der markierten Gruppe MUSS sichtbar sein --
sonst ist die Markierung hinter der zugeklappten Gruppe
verschwunden, und der ganze Umbau waere ein Rueckschritt. */
merkGruppen: gruppen.filter((g) => g.dataset.markiert === "ja").length,
merkSichtbar: [...document.querySelectorAll(".fest-gruppe__kopf .merk")].filter(sichtbar).length,
/* Gegenprobe: Gruppen OHNE Markierung duerfen kein Zeichen haben.
Ein Zeichen an jeder Gruppe zeigt nichts an, es ist Deko. */
merkOhneGrund: gruppen.filter((g) =>
g.dataset.markiert !== "ja" && g.querySelector(".merk")).length,
/* Der Knopf muss mit dem Daumen treffbar sein. */
kopfHoehe: Math.round(
document.querySelector(".fest-gruppe__kopf").getBoundingClientRect().height),
hoeheZu: document.documentElement.scrollHeight,
};
});
ok(zu.gruppen === 3 && zu.zuGeklappt === 3, `alle ${zu.gruppen} Gruppen sind zugeklappt (${zu.zuGeklappt})`);
ok(zu.ariaZu === 3, ` und melden das auch an Vorleseprogramme (aria-expanded=false: ${zu.ariaZu})`);
ok(zu.sichtbarePunkte === 0, ` kein Punkt ist sichtbar (${zu.sichtbarePunkte})`);
ok(zu.merkGruppen === 1, `genau eine Gruppe traegt eine Markierung (${zu.merkGruppen})`);
ok(zu.merkSichtbar === 1, ` und ihr Merkzeichen ist SICHTBAR (${zu.merkSichtbar})`);
ok(zu.merkOhneGrund === 0, ` Gegenprobe: keine Markierung ohne Grund (${zu.merkOhneGrund})`);
ok(zu.kopfHoehe >= 40, ` der Gruppenkopf ist mit dem Daumen treffbar (${zu.kopfHoehe}px)`);
/* Aufklappen: der Koerper geht auf, die Punkte werden sichtbar, und
die Seite wird dabei messbar laenger. Genau dieser Unterschied ist
der Grund fuer den Umbau -- er gehoert also gemessen und nicht
geglaubt. */
await seite.click(".fest-gruppe .fest-gruppe__kopf");
await seite.waitForTimeout(250);
const auf = await seite.evaluate(() => ({
offen: [...document.querySelectorAll(".fest-punkt")].filter((e) => {
const r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0;
}).length,
aria: document.querySelector(".fest-gruppe__kopf").getAttribute("aria-expanded"),
hoehe: document.documentElement.scrollHeight,
}));
ok(auf.offen > 0, `ein Klick auf den Kopf zeigt die Punkte (${auf.offen} sichtbar)`);
ok(auf.aria === "true", " aria-expanded steht auf true");
ok(auf.hoehe > zu.hoeheZu,
` die Seite ist zugeklappt kuerzer: ${zu.hoeheZu}px zu ${auf.hoehe}px `
+ `(${Math.round((1 - zu.hoeheZu / auf.hoehe) * 100)}% gespart)`);
/* Der Zustand ueberlebt das Neuladen -- sonst waere jede Bewertung
ein Rueckschlag zum Anfang der Liste. */
await seite.reload({ waitUntil: "networkidle" });
await seite.waitForTimeout(1100);
const nach = await seite.evaluate(() =>
document.querySelector(".fest-gruppe__kopf")?.getAttribute("aria-expanded"));
ok(nach === "true", ` und ueberlebt das Neuladen (aria-expanded=${nach})`);
/* =================================================================
DAS MERKZEICHEN AUF DER KACHEL
"und es soll auch oben in der Kachel angezeigt werden."
Der Fallstrick steht im Stil: `.kachel` traegt `overflow: hidden`.
Ein Zeichen, das ueber den Rand ragt, waere im Quelltext da und
auf dem Bildschirm abgeschnitten. Geprueft wird deshalb, dass es
VOLLSTAENDIG innerhalb der Kachel liegt.
================================================================= */
await seite.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(1400);
const kach = await seite.evaluate(() => {
const m = document.querySelector(".kachel__merk");
if (!m) return { da: false };
const kachel = m.closest(".kachel");
const a = m.getBoundingClientRect();
const b = kachel.getBoundingClientRect();
return {
da: true,
name: kachel.querySelector(".kachel__name")?.textContent,
sichtbar: a.width > 0 && a.height > 0,
/* 0,5 px Toleranz: Browser runden Teilpixel. */
drin: a.top >= b.top - 0.5 && a.right <= b.right + 0.5
&& a.bottom <= b.bottom + 0.5 && a.left >= b.left - 0.5,
/* Ueberdeckt es etwas anderes auf der Kachel? Zwei Marken
uebereinander sind schlimmer als eine fehlende.
Geprueft wird gegen ALLES, was rechts steht -- Zahl ODER
Pfeil. Nur gegen die Zahl zu pruefen waere wertlos: Traegt die
Kachel ein Merkzeichen, faellt die Zahl weg, und die Pruefung
waere ohne ein einziges Element gruen. Deshalb zaehlt sie die
geprueften Nachbarn mit -- null Nachbarn ist ein Fehler,
kein Erfolg. */
nachbarn: (() => {
const andere = [...kachel.querySelectorAll(".kachel__zahl, .kachel__pfeil")];
let stoss = 0;
for (const e of andere) {
const c = e.getBoundingClientRect();
if (!(a.right < c.left || a.left > c.right
|| a.bottom < c.top || a.top > c.bottom)) stoss++;
}
return { geprueft: andere.length, stoss };
})(),
beschriftung: m.getAttribute("aria-label") || "",
anzahl: document.querySelectorAll(".kachel__merk").length,
};
});
ok(kach.da === true, `die Kachel traegt das Merkzeichen (${kach.name || "-"})`);
ok(kach.sichtbar === true, " es ist sichtbar");
ok(kach.drin === true, " und liegt vollstaendig INNERHALB der Kachel (nicht abgeschnitten)");
ok(kach.nachbarn?.geprueft > 0 && kach.nachbarn?.stoss === 0,
` es ueberdeckt nichts auf der Kachel `
+ `(${kach.nachbarn?.geprueft} Nachbarn geprueft, ${kach.nachbarn?.stoss} Zusammenstoesse)`);
ok(/markiert/i.test(kach.beschriftung),
` Vorleseprogramme bekommen einen Satz ("${kach.beschriftung}")`);
await seite.screenshot({ path: "pruef-merkzeichen.png", fullPage: false });
}
/* Gegenprobe zur Kachel: Wer nichts markiert bekommen hat, darf auch
kein Zeichen sehen. Ein Zeichen, das immer da ist, zeigt nichts an.
Geprueft am Scout -- er hat selbst markiert, aber niemand hat IHM
etwas markiert. */
if (rolle === "scout") {
await seite.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(1400);
const n = await seite.evaluate(() => document.querySelectorAll(".kachel__merk").length);
ok(n === 0, `Gegenprobe: der Scout sieht kein Merkzeichen auf seinen Kacheln (${n})`);
}
await kontext.close();
}
+34
View File
@@ -165,6 +165,40 @@ hinweisRouter.get("/workspace/api/hinweise", (req, res) => {
}
}
/* ---------- Markierte Punkte aus den festen Checklisten ----------
Wunsch vom 01.09.2026: "so dass die sehen, da ist was -- und es
soll auch oben in der Kachel angezeigt werden."
NUR FUER CREATOR, und das ist eine bewusste Entscheidung. Fuer
einen Betreuer waere die Zahl eine Liste dessen, was er selbst
angehakt hat: Sie waechst mit seiner Arbeit, und er kann sie nicht
abbauen -- nur der Creator kann. Ein Zaehler, den man nicht auf
null bringen kann, wird nach zwei Wochen ignoriert, und dann sind
auch die daneben nichts mehr wert.
Vier Bereiche, vier Hinweise, weil vier verschiedene Kacheln
gemeint sind. Ein Sammelhinweis "6 markierte Punkte" liesse offen,
wo man nachsehen soll. */
if (person.rolle === "creator") {
const BEREICHE = [
["live", "LIVE-Analyse", "bereich.html?b=live"],
["content", "Content-Ideen", "content.html"],
["community", "Community", "bereich.html?b=community"],
["technik", "Technik", "bereich.html?b=technik"],
];
for (const [schluessel, name, ziel] of BEREICHE) {
dazu(`markiert_${schluessel}`, "warnung",
`markierter Punkt in ${name}`, ziel,
/* zaehle() faengt Fehler ab -- gibt es die Tabelle noch nicht
(niemand hat je eine Checkliste geoeffnet), steht hier 0
statt eines Ausfalls der ganzen Uebersicht. */
zaehle(`SELECT COUNT(*) n FROM punkt_stand
WHERE bereich = ? AND creator_id = ? AND stufe = 'verbessern'`,
[schluessel, person.id]));
}
}
/* ---------- Scout-Pipeline ---------- */
if (istLeitung(person) || person.rolle === "scout") {
const nur = istLeitung(person) ? "1=1" : "l.scout_id = ?";