Kapitel 11 auf der Crew-Adresse: die Community gehoert nicht in die Pflegeliste

ZWEITER BEFUND DES ABENDS, und wieder einer, den es im Agenturhaus
gar nicht geben konnte.

Bauplan Kapitel 11, woertlich: "Stand der Ersten Schritte: nur fuer
Team-Rollen, 'fertig / offen' mit Datum. Fuer die Community wird
nichts je Person angezeigt."

Gemessen: Auf der Crew-Adresse nahm die Liste schlicht alle Fassungen
des Hauses -- und seit die Community seit heute frueh eine eigene
Fassung hat, stand jedes Mitglied mit Namen, Fortschritt und Datum
darin. Im Agenturhaus konnte das nie auffallen, dort ist jede Fassung
eine Teamrolle.

Das ist kein Schoenheitsfehler: Ein Mitglied hat sich zum Mitlesen
angemeldet, nicht zu einer Fortschrittsakte. Jetzt siebt
`standRollen()` ueber AUSSEN_ROLLEN -- abgeleitet und nicht als zweite
Liste, damit die naechste Rolle von aussen automatisch heraus ist.
Gegenprobe gefahren: Sieb ausgebaut -> die Zeile wird rot und nennt
"1 von 4".

DAZU GEPRUEFT, was Kapitel 11 sonst verlangt (pruef-anleitung
173 -> 181): DogFather bekommt auf der Crew-Adresse die Pflege statt
einer Fassung, mit den VIER Fassungen dieses Hauses (nicht denen der
Agentur); ein Modi kommt an die Liste gar nicht heran (403); und die
"Vorschau in der Sicht dieser Rolle" liefert ihm die 29 Karten des
Modi, mit `darf_pflegen: true` und `darf_abhaken: false` -- er soll
pflegen koennen, aber nicht in fremdem Namen abhaken. Nachgestellt
wird dabei genau die Anfrage, die kopf.js stellt (`sicht=<Nummer>`
an jede lesende Anfrage), nicht eine nachgebaute.

AUSSERDEM: ZWEI DINGE HIESSEN `an-stufe`
Der Kasten einer EINORDNUNG ("Das brauchst du sofort") und der Kasten
"Deine Stufe" mit Neu, Dabei, Stamm trugen dieselbe Klasse. Der
Bauplan warnt in Kapitel 14 ausdruecklich davor, die beiden zu
verwechseln -- im Quelltext taten sie es.

Aufgefallen beim Messen des Filters "Muss": Er musste seine Suche von
Hand auf `#stufen` eingrenzen, sonst haette er die Karte "Deine Stufe"
mit weggeblendet. Eine Regel wie `.an-stufe { display: none }` haette
denselben Schaden angerichtet, und man haette sie nicht kommen sehen.
Die Mitglieder-Stufe heisst jetzt `an-rang`; in der Oberflaeche aendert
sich kein Wort.

IM BROWSER NACHGEMESSEN (390 px, echte Anmeldung)
  Filter "Muss"  -> Community 5 Karten, rechte Hand 8. Genau die
                    Zahlen der Abnahme-Checkliste, und nach dem
                    Umbenennen blendet er nur noch den einen
                    Abschnitt aus statt zwei.
  "Deine Stufe"  -> Community ja ("Du bist gerade: Neu"), rechte Hand
                    nein. Steht und faellt mit dem neuen Namen.
  Keine Browsermeldungen, kein Querscrollen.

pruef-css-klassen und pruef-anleitung (181) gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-06 17:49:18 +02:00
co-authored by Claude Opus 5
parent a7b5775268
commit 88a8d302b9
51 changed files with 842 additions and 707 deletions
+22
View File
@@ -242,6 +242,28 @@ for (const [rolle, wie] of ROLLEN) {
console.log(` Querscrollen : ${lage.quer ? "JA -- Befund" : "nein"}`);
console.log(` Tippziele unter 44 : ${lage.klein}`);
console.log(` Stufen-Marken : ${lage.marken.join(" | ") || "keine"}`);
/* ==== DER FILTER „MUSS" (Abnahme Kapitel 13) =====================
„Filter ‚Muss' zeigt nur die Muss-Karten." Gemessen wird, was
nach dem Tippen SICHTBAR ist -- nicht, was im Baum steht. Ein
Kasten mit `hidden` enthaelt seine Karten weiterhin; wer sie
zaehlt, zaehlt auch die verborgenen und bekommt immer dieselbe
Zahl heraus. */
const filter = await seite.evaluate(async () => {
const knopf = [...document.querySelectorAll(".an-filter__knopf")]
.find((k) => k.dataset.wert === "muss");
if (!knopf) return { da: false };
knopf.click();
await new Promise((r) => setTimeout(r, 200));
const sichtbar = [...document.querySelectorAll(".an-karte")]
.filter((k) => k.getBoundingClientRect().height > 0).length;
const offeneStufen = [...document.querySelectorAll(".an-stufe")]
.filter((b) => !b.hidden).map((b) => b.dataset.stufe);
return { da: true, sichtbar, stufen: offeneStufen.join(", ") };
});
console.log(` Filter „Muss“ : ${filter.da
? `${filter.sichtbar} Karten sichtbar, Abschnitt(e): ${filter.stufen}`
: "KEIN FILTERKNOPF -- Befund"}`);
console.log(` Browsermeldungen : ${klagen.length ? klagen.join(" | ") : "keine"}`);
console.log(` Fehlgeschlagen : ${schlecht.length ? schlecht.slice(0, 6).join(" | ") : "nichts"}`);
+69
View File
@@ -1290,6 +1290,75 @@ melde("\n=== Seite sperren, Karte weg (Abnahme Kapitel 13) ===");
+ `${vorher.length})`);
}
/* =====================================================================
DIE PFLEGE AUF DER CREW-ADRESSE (Bauplan Kapitel 11)
---------------------------------------------------------------------
„Fuer DogFather gibt es keine Fassung und keinen Rundgang. Bei ihm
oeffnet die Kachel die Pflege-Ansicht: Rolle waehlen und Vorschau,
Einordnung aendern, Texte bearbeiten, Lueckenwarnung, Stand der
Ersten Schritte — nur fuer Team-Rollen, fuer die Community wird
nichts je Person angezeigt."
DER LETZTE HALBSATZ IST DER, DER HIER GEMESSEN WIRD. Im Agenturhaus
konnte er nie falsch sein (dort ist jede Fassung eine Teamrolle);
auf der Crew-Adresse schon, seit die Community eine eigene Fassung
hat. Ein Mitglied hat sich zum Mitlesen angemeldet, nicht zu einer
Fortschrittsakte mit Namen und Datum. */
melde("\n=== Die Pflege auf der Crew-Adresse (Kapitel 11) ===");
{
const dogiCrew = await holen("admin", CREW_HOST);
ok(dogiCrew.code === 200 && dogiCrew.j?.hat_fassung === false
&& dogiCrew.j?.darf_pflegen === true,
`DogFather bekommt auf der Crew-Adresse die Pflege, keine Fassung `
+ `(HTTP ${dogiCrew.code}, Fassung ${dogiCrew.j?.hat_fassung}, `
+ `pflegen ${dogiCrew.j?.darf_pflegen})`);
/* Und zwar die VIER des Hauses, nicht die der Agentur. */
const angeboten = (dogiCrew.j?.fassungen || []).join(",");
ok(angeboten === "gast,modi,linke,hand",
`…mit den vier Fassungen dieses Hauses (${angeboten || "keine"})`);
const st = await roh("/workspace/api/anleitung/stand", CREW_HOST, null, kekse.admin);
let leute = [];
try { leute = JSON.parse(st.text)?.leute || []; } catch { /* bleibt leer */ }
ok(st.code === 200 && leute.length > 0,
`der Stand der Ersten Schritte oeffnet sich (HTTP ${st.code}, `
+ `${leute.length} Leute)`);
ok(!leute.some((p) => p.rolle === "gast"),
`…und kein einziges Mitglied steht darin (${
leute.filter((p) => p.rolle === "gast").length} von ${leute.length})`);
/* GEGENPROBE: Es steht ueberhaupt jemand drin, und zwar aus dem
Team. Ohne diese Zeile waere die darueber auch dann gruen, wenn
die Liste aus einem ganz anderen Grund leer bliebe. */
ok(leute.some((p) => ["modi", "linke", "hand"].includes(p.rolle)),
`Gegenprobe: die Teamrollen stehen sehr wohl darin (${
leute.map((p) => p.rolle).join(", ") || "niemand"})`);
/* Und ein Modi kommt gar nicht erst an die Liste. */
const stModi = await roh("/workspace/api/anleitung/stand", CREW_HOST, null, kekse.modi);
ok(stModi.code === 403, `ein Modi sieht den Stand nicht (${stModi.code})`);
/* ---- „Rolle waehlen und Vorschau in der Sicht dieser Rolle" -------
Gebaut ist das nicht als zweite Rollenauswahl, sondern ueber den
Sicht-Umschalter, den es im Haus schon gibt: kopf.js haengt jeder
LESENDEN Anfrage `sicht=<Nummer>` an. Genau das wird hier
nachgestellt -- nicht nachgebaut, sondern dieselbe Anfrage
gestellt, die der Browser stellt. */
const modiId = (() => {
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const z = d.prepare("SELECT id FROM personen WHERE rolle = 'modi'").get();
d.close(); return z?.id || 0;
})();
braucht(!!modiId, "kein Modi in der Probe-Datenbank");
const vorschau = await roh(`/workspace/api/anleitung?sicht=${modiId}`,
CREW_HOST, null, kekse.admin);
let vj = null; try { vj = JSON.parse(vorschau.text); } catch { /* bleibt null */ }
ok(vj?.rolle === "modi" && (vj?.karten?.length ?? 0) === 29,
`in der Sicht auf einen Modi sieht er dessen Anleitung `
+ `(${vj?.rolle}, ${vj?.karten?.length ?? 0} Karten)`);
ok(vj?.darf_pflegen === true && vj?.darf_abhaken === false,
`…darf sie pflegen, aber nicht in fremdem Namen abhaken `
+ `(pflegen ${vj?.darf_pflegen}, abhaken ${vj?.darf_abhaken})`);
}
/* ---------- Abschluss ------------------------------------------------ */
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(`\n${fehler ? "FEHLER: " + fehler : "Alles in Ordnung"} — ${geprueft} Prüfungen`);
+30 -2
View File
@@ -615,8 +615,36 @@ anleitungRouter.put("/workspace/api/anleitung/rolle/:rolle", nurPflege, (req, re
* Unterscheidung wie bei der Team-Lage. Hier steht, wer seine
* Einweisung noch offen hat, damit jemand nachfragen kann. Nicht,
* was er gelesen hat oder wie lange er gebraucht hat. */
/** Welche Rollen stehen im „Stand der Ersten Schritte"?
*
* NUR TEAMROLLEN -- Bauplan Kapitel 11, woertlich: „Stand der Ersten
* Schritte: nur fuer Team-Rollen, ‚fertig / offen' mit Datum. Fuer die
* Community wird nichts je Person angezeigt."
*
* GEMESSEN AM 06.10.2026, UND ES STIMMTE NICHT: Auf der Crew-Adresse
* nahm die Liste schlicht alle Fassungen des Hauses -- und seit die
* Community eine eigene Fassung hat, stand jedes Mitglied mit Namen
* und Fortschritt darin. Das ist kein Schoenheitsfehler: Ein Mitglied
* hat sich zum Mitlesen angemeldet, nicht zu einer Fortschrittsakte.
* Im Agenturhaus fiel es nicht auf, weil dort jede Fassung eine
* Teamrolle ist.
*
* ABGELEITET AUS AUSSEN_ROLLEN und nicht aus einer zweiten Liste:
* Kommt eines Tages eine weitere Rolle von aussen dazu, ist sie damit
* automatisch heraus. Eine Liste hier waere die, die man beim
* Anlegen der neuen Rolle nicht anfasst. */
function standRollen(haus) {
return fassungenFuer(haus).filter((r) => !AUSSEN_ROLLEN.has(r));
}
anleitungRouter.get("/workspace/api/anleitung/stand", nurPflege, (req, res) => {
try {
const rollen = standRollen(hausVon(req.person));
/* Keine Teamrolle in diesem Haus? Dann eine leere Liste und kein
SQL mit leerem IN () -- das waere ein Syntaxfehler und damit ein
500 an einer Stelle, an der „es gibt niemanden" die richtige
Antwort ist. */
if (!rollen.length) return res.json({ leute: [] });
const leute = db().prepare(
`SELECT p.id, p.name, p.rolle,
(SELECT COUNT(*) FROM anl_zeile z
@@ -625,8 +653,8 @@ anleitungRouter.get("/workspace/api/anleitung/stand", nurPflege, (req, res) => {
WHERE s.person_id = p.id AND z2.art = 'schritt') AS erledigt,
(SELECT MAX(s2.erledigt_am) FROM anl_stand s2 WHERE s2.person_id = p.id) AS zuletzt
FROM personen p
WHERE p.aktiv = 1 AND p.rolle IN (${fassungenFuer(hausVon(req.person)).map(() => "?").join(",")})
ORDER BY p.rolle, p.name`).all(hausVon(req.person), ...fassungenFuer(hausVon(req.person)));
WHERE p.aktiv = 1 AND p.rolle IN (${rollen.map(() => "?").join(",")})
ORDER BY p.rolle, p.name`).all(hausVon(req.person), ...rollen);
res.json({
leute: leute.map((p) => ({
name: p.name, rolle: p.rolle,