diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs
index 82cfd4bc..50b009ad 100644
--- a/server/pruef-bewerbung-aufgaben.mjs
+++ b/server/pruef-bewerbung-aufgaben.mjs
@@ -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
diff --git a/server/workspace-zuteilung.js b/server/workspace-zuteilung.js
index af25c714..b741e41d 100644
--- a/server/workspace-zuteilung.js
+++ b/server/workspace-zuteilung.js
@@ -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
diff --git a/server/workspace.js b/server/workspace.js
index ada98600..cfd85c9c 100644
--- a/server/workspace.js
+++ b/server/workspace.js
@@ -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
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index 525ca177..aa903e2f 100644
--- a/workspace/anruf-probe.html
+++ b/workspace/anruf-probe.html
@@ -16,15 +16,15 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
-
-
+
+
-
+