screen1 + A5 + B6: „wer zuerst Zeit hat" gilt wieder, und Aufgaben entstehen dort, wo die Person steht

DREI SACHEN, EIN ZUSAMMENHANG.

--- screen1 --------------------------------------------------------
Filipe zu meinem eigenen Satz „wer zuerst Zeit hat — genau das geht ja
nicht mehr": „falls das nicht mehr geht mach das es geht wenn es wieder
geht ist alles gut."

Er hat recht, und ich hatte zu schnell aufgegeben. „Wer zuerst Zeit
hat" muss nicht heissen, dass man sich selbst bedient -- es kann
genauso heissen, dass die ERSTE BEWERBUNG zuerst drankommt. Damit gilt
beides: Die Leitung entscheidet (sein Wunsch von vorhin), und wer
schnell ist, hat den Vorteil (sein Wunsch von eben).

Die Bewerbungen werden jetzt nach EINGANG sortiert, nicht nach Namen
-- die Abfrage sortiert sonst alphabetisch, und dann haette nicht der
Schnelle den Vorteil, sondern Frida. Der Erste bekommt die Marke
„zuerst da" (nur bei mehr als einer -- sonst ist es keine Auskunft,
sondern Fuellwerk).

Geprueft mit Absicht gegen das Alphabet: Nele bewirbt sich zuerst und
steht vorn, obwohl Frida im Alphabet vor ihr kaeme.

--- B6: der Knopf auf dem Aufgabenbrett ----------------------------
Filipe: „dieser button kann da jetzt doch endlich verschwinden, auf
dieser seite sollen ja keine aufgaben mehr verteilt werden."

ER HAT DABEI AUF DEN TEAM-DOGI-BILDSCHIRM GESEHEN, und das ist der
Unterschied zwischen „weg damit" und „weg damit, aber gemessen":

    Rolle/Haus        aufgaben.html  entwicklung.html  darfAnlegen
    manager/agentur   ja             NEIN              ja
    creator/agentur   ja             NEIN              ja
    scout/agentur     ja             NEIN              ja
    spicy/agentur     ja             NEIN              ja

Haette ich den Knopf einfach entfernt, koennten vier Rollen gar keine
Aufgabe mehr anlegen. Der Server nennt deshalb den ORT, und er leitet
ihn aus den KACHELN der Person ab -- nicht aus dem Seitenrecht (dann
haette DogFather auf der Agenturadresse den Knopf verloren, denn
oeffnen darf er die Seite, nur hat er dort keine Kachel dorthin) und
schon gar nicht aus dem Haus.

--- A5: anlegen, wo die Person steht -------------------------------
Filipe: „dieser buttion da soll nicht einen zu der seite aufgaben
fuehren sondern da in dieser seite die aufgaben erstellen und vergeben
koennen. plus man soll die aufgaben hier in dieser seite auch sehen."

Der Knopf war ein Link auf `aufgaben.html?neu=1`. Jetzt oeffnet er ein
Formular an Ort und Stelle. DREI FELDER, NICHT ZWOELF: Wer hier steht,
hat die Person schon gewaehlt; was fehlt, ist was, bis wann und ob
noch jemand mitmacht. Das grosse Formular auf dem Brett bleibt fuer
den Fall, dass man eine Aufgabe fuer irgendwen irgendwo anlegt -- zwei
vollstaendige Masken fuer dieselbe Sache waeren zwei Gelegenheiten,
eine davon zu vergessen.

Dazu die Aufgaben der gewaehlten Person, auf derselben Seite. Sie
kommen beim Auswaehlen und nicht auf Knopfdruck: Wer jemanden
durchgeht, will wissen, was bei ihm liegt.

Ohne Person ist der Knopf AUS statt weg -- sonst springt die Seite beim
Auswaehlen -- und der Satz daneben sagt, was fehlt.

Gemeldet wird, was der Server WIRKLICH gesetzt hat: `zuteilen` kann
eine Person weglassen (gesperrter Zugang). Wer das verschweigt, laesst
jemanden im Glauben, er habe drei Leute eingetragen.

--- EIN FUND AUF DEM EIGENEN BILDSCHIRMFOTO ------------------------
Die Bestaetigung „Clips vom Samstag schneiden steht jetzt bei Rieke."
stand in WARNROT -- `melde` schreibt immer in denselben Absatz, und
der heisst `.fehler`. Nicht schlimm und genau deshalb heimtueckisch:
Wer Bestaetigungen in Rot liest, sucht den Fehler, und wer sich daran
gewoehnt, ueberliest die echte Warnung. `melde` kennt jetzt eine gute
Nachricht; die Vorgabe bleibt „Fehler", damit die neun vorhandenen
Aufrufe nicht stillschweigend umgefaerbt werden.

GEPRUEFT
pruef-bewerbung-aufgaben 84/0 (von 66) -- neu: die Reihenfolge nach
Eingang mit Gegenprobe gegen das Alphabet, und ein Abschnitt, der A5
und B6 am echten Bildschirm durchspielt (Knopf weg auf dem Brett, kein
Link mehr auf der Entwicklungsseite, Formular oeffnet dort, Aufgabe
steht sofort in der Liste darunter UND wirklich in der Liste des
Servers, Bestaetigung als gute Nachricht gekennzeichnet).

Gruen: pruef-entwicklung, pruef-aufgabenbrett, pruef-css-klassen,
pruef-formulare, pruef-struktur.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-22 19:09:16 +02:00
co-authored by Claude Opus 5
parent 43545e4acd
commit 3b76a9aab5
47 changed files with 1324 additions and 589 deletions
+130 -2
View File
@@ -285,6 +285,47 @@ melde("=== 4. Bewerben ===");
/* =====================================================================
5. ENTSCHEIDEN — nur die rechte Hand und DogFather
===================================================================== */
melde("");
melde("=== 4b. Wer zuerst Zeit hat, steht zuerst ===");
{
/* Filipe, nachdem er meinen Satz „wer zuerst Zeit hat — genau das
geht ja nicht mehr" gelesen hatte: „falls das nicht mehr geht mach
das es geht wenn es wieder geht ist alles gut."
„Wer zuerst Zeit hat" heisst nicht zwingend „man bedient sich
selbst" -- es kann genauso heissen, dass die ERSTE BEWERBUNG zuerst
drankommt. Damit gilt beides gleichzeitig.
GEPRUEFT WIRD DIE REIHENFOLGE, NICHT DER SATZ. Die Abfrage
sortiert sonst nach NAMEN; nach Alphabet haette nicht der
Schnelle den Vorteil, sondern Frida. Genau deshalb steht hier
eine Messung und kein Blick in den Quelltext. */
const r = await aufgabeAnlegen(chef.keks, "Wer zuerst kommt", [idModi, idModi2], "pool");
const id = r.json?.id;
ok(r.code === 201, `eine neue Pool-Aufgabe (${r.code})`);
/* NELE ZUERST -- und sie steht im Alphabet HINTER Frida. Waere die
Liste nach Namen sortiert, faellt genau das auf. */
const n1 = await roh(`/workspace/api/aufgaben/${id}/bewerben`, {
methode: "POST", keks: modi2.keks, body: { text: "Ich kann sofort." } });
ok(n1.code === 200, `Nele bewirbt sich zuerst (${n1.code})`);
await new Promise((w) => setTimeout(w, 1100));
const n2 = await roh(`/workspace/api/aufgaben/${id}/bewerben`, {
methode: "POST", keks: modi.keks, body: { text: "Ich auch." } });
ok(n2.code === 200, `Frida danach (${n2.code})`);
const a = await finde(chef.keks, "Wer zuerst kommt");
const reihe = (a?.bewerbungen || []).map((z) => z.person_name);
ok(reihe.length === 2, `zwei Bewerbungen liegen vor (${reihe.length})`);
ok(reihe[0] === "Nele",
`Nele steht vorn, weil sie zuerst da war (${reihe.join(" vor ")})`);
/* GEGENPROBE: Nach Namen sortiert stuende Frida vorn. Die Messung
kann den Unterschied also sehen -- ohne diese Zeile waere
„Nele steht vorn" auch bei einer zufaelligen Ordnung mal richtig. */
ok([...reihe].sort()[0] === "Frida",
"nach Alphabet staende Frida vorn — die Reihenfolge ist also wirklich die des Eingangs");
}
melde("");
melde("=== 5. Entscheiden ===");
{
@@ -541,6 +582,93 @@ melde("=== 6. Am echten Bildschirm ===");
`und keinen Block zum Entscheiden (${b?.bewerbungen})`);
} finally { await m2.ctx.close(); }
/* =================================================================
7. AUFGABEN DORT ANLEGEN, WO DIE PERSON STEHT (A5 und B6)
=================================================================
Filipe: „dieser buttion da soll nicht einen zu der seite
aufgaben fuehren sondern da in dieser seite die aufgaben
erstellen und vergeben koennen. plus man soll die aufgaben hier
in dieser seite auch sehen." Und zum Aufgabenbrett: „dieser
button kann da jetzt doch endlich verschwinden."
DER ORT WIRD GEMESSEN, NICHT ANGENOMMEN: Auf der Agenturadresse
gibt es die Entwicklungsseite nicht, und vier Rollen dort
duerfen sehr wohl Aufgaben anlegen. Der Knopf verschwindet also
genau dort, wo es einen anderen Ort gibt -- und nur dort.
================================================================= */
const e = await seiteAls("admin", "CODE-DOGI-0001", 1180);
try {
/* B6: auf dem Brett ist der Knopf weg. */
const b6 = await e.s.evaluate(() => {
const k2 = document.getElementById("neu-oeffnen");
return { da: !!k2, versteckt: k2 ? k2.hidden : null,
ort: window.__ich?.aufgaben_anlegen_auf };
});
ok(b6.ort === "entwicklung",
`der Server nennt die Entwicklungsseite als Ort (${b6.ort})`);
ok(b6.da && b6.versteckt === true,
`und der Knopf auf dem Aufgabenbrett ist weg (${b6.versteckt})`);
/* A5: auf der Entwicklungsseite geht es jetzt. */
await e.s.goto(`${SEITE}/workspace/entwicklung.html`, { waitUntil: "networkidle" });
await e.s.waitForTimeout(1800);
const vorher = await e.s.evaluate(() => {
const k2 = document.getElementById("verteilen-knopf");
return { istLink: k2?.tagName === "A", aus: k2?.disabled,
aufgabenBlock: !document.getElementById("e-aufgaben-block")?.hidden };
});
ok(vorher.istLink === false,
"der Knopf fuehrt nicht mehr auf eine andere Seite (kein Link mehr)");
ok(vorher.aus === true,
"und ist aus, solange niemand gewaehlt ist — er haette nichts zu verteilen");
ok(vorher.aufgabenBlock === false,
"ohne Person steht auch keine Aufgabenliste da");
await e.s.click("#personen .e-person");
await e.s.waitForTimeout(1400);
const nachher = await e.s.evaluate(() => ({
knopf: document.getElementById("verteilen-knopf")?.textContent.trim(),
aus: document.getElementById("verteilen-knopf")?.disabled,
block: !document.getElementById("e-aufgaben-block")?.hidden,
titel: document.getElementById("e-aufgaben-titel")?.textContent.trim(),
}));
ok(nachher.aus === false && /Aufgabe für/.test(nachher.knopf || ""),
`nach der Auswahl heisst er „${nachher.knopf}"`);
ok(nachher.block === true && /Aufgaben von/.test(nachher.titel || ""),
`und die Aufgabenliste steht da („${nachher.titel}")`);
await e.s.click("#verteilen-knopf");
await e.s.waitForTimeout(500);
const form = await e.s.evaluate(() => ({
offen: !document.getElementById("e-neu")?.hidden,
felder: document.querySelectorAll("#e-neu input, #e-neu textarea").length,
}));
ok(form.offen === true && form.felder >= 3,
`das Formular oeffnet sich auf derselben Seite (${form.felder} Felder)`);
const titel = "Auf der Entwicklungsseite angelegt";
await e.s.fill("#e-titel", titel);
await e.s.click("#e-neu-speichern");
await e.s.waitForTimeout(1800);
const fertig = await e.s.evaluate((t) => ({
zu: document.getElementById("e-neu")?.hidden,
steht: [...document.querySelectorAll(".e-aufgabe__titel")].some((x) => x.textContent === t),
gut: document.getElementById("fehler")?.dataset.gut,
}), titel);
ok(fertig.zu === true, "nach dem Anlegen ist das Formular zu");
ok(fertig.steht === true, "und die Aufgabe steht sofort in der Liste darunter");
/* EINE BESTAETIGUNG DARF NICHT AUSSEHEN WIE EIN FEHLER. Auf dem
ersten Bildschirmfoto stand sie in Warnrot -- `melde` schreibt
immer in denselben Absatz, und der heisst `.fehler`. */
ok(fertig.gut === "ja",
`und die Bestaetigung ist als gute Nachricht gekennzeichnet (${fertig.gut})`);
/* GEGENPROBE: Die Aufgabe ist wirklich da, nicht nur im Bild. */
const ausDerListe = await finde(chef.keks, titel);
ok(!!ausDerListe, "sie steht auch wirklich in der Aufgabenliste des Servers");
} finally { await e.ctx.close(); }
ok(meckern.length === 0, meckern.length
? `der Browser meldet: ${meckern.slice(0, 3).join(" | ")}`
: "kein Absturz und keine Fehlermeldung im Browser");
@@ -554,8 +682,8 @@ melde("=== 6. Am echten Bildschirm ===");
ERGEBNIS
===================================================================== */
melde("");
if (geprueft < 55) {
console.log(` FEHL nur ${geprueft} Pruefungen gelaufen -- erwartet waren 55+`);
if (geprueft < 70) {
console.log(` FEHL nur ${geprueft} Pruefungen gelaufen -- erwartet waren 70+`);
fehler++;
}
melde(fehler === 0
+24 -1
View File
@@ -183,7 +183,30 @@ export function mitZuteilung(reihen, ich) {
/* DIE OFFENEN BEWERBUNGEN, abgeleitet statt danebengefuehrt.
Die Oberflaeche soll nicht dieselbe Rechnung noch einmal machen
-- zwei Rechnungen fuer dieselbe Frage laufen auseinander. */
r.bewerbungen = zu.filter((z) => z.zustand === "beworben");
/* NACH EINGANG SORTIERT, NICHT NACH NAMEN (22.09.2026).
Filipe zu meinem eigenen Satz „wer zuerst Zeit hat — genau das
geht ja nicht mehr": „falls das nicht mehr geht mach das es
geht wenn es wieder geht ist alles gut."
Er hat recht, und ich hatte es zu schnell aufgegeben. „Wer
zuerst Zeit hat" muss nicht heissen, dass man sich selbst
bedient -- es kann genauso heissen, dass die ERSTE BEWERBUNG
zuerst drankommt. Damit gilt beides: Die Leitung entscheidet
(sein Wunsch von vorhin), und wer schnell ist, hat den Vorteil
(sein Wunsch von eben).
Die Abfrage weiter oben sortiert nach NAMEN -- gut fuer die
Liste der Zugeteilten, falsch hier: Nach Alphabet gaebe es
keinen Vorteil fuer den Schnellen, sondern einen fuer Anna.
`geantwortet_am` ist der Zeitpunkt der Bewerbung (beim
Bewerben gesetzt). Gleichstand gibt es praktisch nicht; falls
doch, entscheidet die laufende Nummer -- irgendeine Ordnung
ist besser als eine zufaellige. */
r.bewerbungen = zu.filter((z) => z.zustand === "beworben")
.sort((x, y) => String(x.geantwortet_am || "").localeCompare(String(y.geantwortet_am || ""))
|| (x.id - y.id));
/* UND OB ICH MICH UEBERHAUPT NOCH BEWERBEN KANN. Ein Knopf, der
beim Druecken eine Absage bringt, ist schlimmer als keiner. */
r.ich_beworben = !!(r.meine_zuteilung
+41
View File
@@ -5941,6 +5941,47 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => {
/* Damit die Oberflaeche den Knopf gar nicht erst anbietet -- ein
Knopf, der mit 403 antwortet, ist schlimmer als keiner. */
darf_aufgaben_anlegen: darfAufgabenAnlegen(person),
/* WO AUFGABEN ANGELEGT WERDEN (22.09.2026).
Filipe, zum Knopf auf dem Aufgabenbrett: „dieser button kann da
jetzt doch endlich verschwinden, auf dieser seite sollen ja
keine aufgaben mehr verteilt werden." Und zur
Entwicklungsseite: „dieser buttion da soll nicht einen zu der
seite aufgaben fuehren sondern da in dieser seite die aufgaben
erstellen und vergeben koennen."
ABER NICHT UEBERALL, UND DAS IST GEMESSEN. Die Entwicklungs-
seite steht vier Rollen gar nicht offen, die sehr wohl Aufgaben
anlegen duerfen:
Rolle/Haus aufgaben.html entwicklung.html darfAnlegen
manager/agentur ja NEIN ja
creator/agentur ja NEIN ja
scout/agentur ja NEIN ja
spicy/agentur ja NEIN ja
Haette ich den Knopf einfach entfernt, koennten vier Rollen gar
keine Aufgabe mehr anlegen -- und Filipe hat beim Schreiben auf
den Team-Dogi-Bildschirm gesehen, nicht auf ihren.
DER ORT WIRD AUS DEN KACHELN ABGELEITET, nicht aus dem
Seitenrecht und schon gar nicht aus dem Haus.
Der erste Entwurf fragte `darfSeite(..., entwicklung.html)`.
Gemessen war das falsch: DogFather DARF die Seite auf der
Agenturadresse oeffnen (er darf alles), hat dort aber keine
Kachel dorthin -- der Knopf waere ihm weggenommen worden und
der Ersatz unauffindbar gewesen.
Massgeblich ist deshalb, ob die Entwicklungsseite ueberhaupt zu
seinen Bereichen gehoert. Das ist dieselbe Quelle, aus der die
Startseite ihre Kacheln baut -- eine zweite Liste waere die,
die auseinanderlaeuft. */
aufgaben_anlegen_auf: [
...(bereicheFuer(person) || []),
...(zusatzBereicheFuer(person) || []),
].some((k) => String(k.ziel || "").startsWith("entwicklung.html"))
? "entwicklung" : "aufgaben",
/* FUEHRT JEMAND TEAM DOGI? (22.09.2026)
Damit die Entwicklungsseite die Kartenliste GAR NICHT ERST
abfragt, wenn es sie fuer diese Person nicht gibt. Der Code