diff --git a/server/pruef-reaktion.mjs b/server/pruef-reaktion.mjs
index f04d8ddd..5a12fd43 100644
--- a/server/pruef-reaktion.mjs
+++ b/server/pruef-reaktion.mjs
@@ -174,18 +174,59 @@ melde("=== 2. Die Agentur kennt sie nicht ===");
melde("");
melde("=== 3. Wer die Sendung fuehrt ===");
{
- for (const rolle of ["hand", "linke", "modi", "gast"]) {
+ /* ZWEI FUEHREN, UND NUR DIE ZWEI (Filipe, 28.09.2026):
+ „die rolle dogfather und vanvan sollen alles sehen und bereit
+ machen auch vorher. nur die zwei sollen alles sehen, vorbereiten
+ und einschakten koennen."
+
+ Geprueft wird deshalb in BEIDE Richtungen -- wer darf, und wer
+ ausdruecklich nicht. Nur die eine Haelfte zu messen hiesse: Die
+ Tuer laesst sich spaeter weit aufmachen, ohne dass etwas rot
+ wird. */
+ const { REAKTION_HOST_ROLLEN } = await import("./workspace-reaktion.js");
+ ok(REAKTION_HOST_ROLLEN.has("admin") && REAKTION_HOST_ROLLEN.has("hand")
+ && REAKTION_HOST_ROLLEN.size === 2,
+ `genau zwei fuehren die Sendung: ${[...REAKTION_HOST_ROLLEN].join(", ")}`);
+
+ for (const rolle of ["linke", "modi", "gast"]) {
const a = await roh(CREW, "/workspace/api/reaktion", {
method: "PUT", keks: k[rolle], rumpf: { titel: "Nicht du" },
});
ok(a.code === 403, `${rolle}: darf nicht einstellen (${a.code})`);
}
+
+ /* Die rechte Hand darf ALLES, was zum Fuehren gehoert -- nicht nur
+ einstellen. Ein Knopf, der da ist und mit 403 antwortet, ist eine
+ Falle; deshalb wird jeder einzeln nachgefahren. */
+ const wege = [
+ ["einstellen", "PUT", "/workspace/api/reaktion", { titel: "VanVan richtet ein" }],
+ ["vorbereiten", "POST", "/workspace/api/reaktion/stand", { stand: "vorbereitung" }],
+ ["Anordnung", "POST", "/workspace/api/reaktion/layout", { layout: "gleich" }],
+ ];
+ for (const [was, art, pfad, rumpf] of wege) {
+ const a = await roh(CREW, pfad, { method: art, keks: k.hand, rumpf });
+ ok(a.code === 200, `die rechte Hand darf ${was} (${a.code})`);
+ }
+ await roh(CREW, "/workspace/api/reaktion/stand", { method: "POST", keks: k.hand, rumpf: { stand: "zu" } });
+ await roh(CREW, "/workspace/api/reaktion/layout", { method: "POST", keks: k.admin, rumpf: { layout: "kino" } });
+
const a = await roh(CREW, "/workspace/api/reaktion", {
method: "PUT", keks: k.admin,
rumpf: { titel: "Wir schauen den Trailer", video: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
});
ok(a.code === 200 && a.j?.video === "dQw4w9WgXcQ",
- `DogFather schon -- und aus der Adresse wird die Kennung (${a.j?.video})`);
+ `DogFather auch -- und aus der Adresse wird die Kennung (${a.j?.video})`);
+
+ /* DAS REGIEPULT HAENGT AN `ich.host` UND AN NICHTS SONST. Ein
+ zweiter Ort, an dem der Browser dieselbe Frage noch einmal
+ beantwortet, waere der, der spaeter abweicht -- und dann stuenden
+ Knoepfe da, die mit 403 antworten. */
+ for (const [rolle, soll] of [["admin", true], ["hand", true],
+ ["linke", false], ["modi", false], ["gast", false]]) {
+ const b = await roh(CREW, "/workspace/api/reaktion", { keks: k[rolle] });
+ ok(b.j?.ich?.host === soll,
+ `${rolle}: ${soll ? "sieht" : "sieht kein"} Regiepult (ich.host=${b.j?.ich?.host})`);
+ }
}
/* ---- Die Kennung kommt aus jeder Form, die man wirklich einfuegt ---- */
@@ -240,6 +281,32 @@ melde("=== 4. zu -> vorbereitung -> live -> zu ===");
});
ok(e.code === 200 && e.j?.stand === "live" && e.j?.gestartet_am,
`auf Sendung -- mit Startzeit (${e.j?.stand})`);
+
+ /* WER EINSCHALTET, IST IM BILD.
+
+ Vorher stand als Host, wer zuletzt etwas eingestellt hatte. Mit
+ zwei Leuten, die vorbereiten duerfen, ist das eine Falle: VanVan
+ richtet nachmittags ein, Filipe drueckt abends auf Sendung -- und
+ im Bild stuende VanVans Name, waehrend Filipe redet. */
+ ok(e.j?.host?.id === IDS.admin,
+ `wer eingeschaltet hat, fuehrt die Sendung (${e.j?.host?.name})`);
+
+ await roh(CREW, "/workspace/api/reaktion/stand", { method: "POST", keks: k.admin, rumpf: { stand: "zu" } });
+ await roh(CREW, "/workspace/api/reaktion", {
+ method: "PUT", keks: k.admin, rumpf: { titel: "Wir schauen den Trailer", video: "dQw4w9WgXcQ" },
+ });
+ const f = await roh(CREW, "/workspace/api/reaktion/stand", {
+ method: "POST", keks: k.hand, rumpf: { stand: "live" },
+ });
+ ok(f.j?.host?.id === IDS.hand,
+ `drueckt die rechte Hand, fuehrt sie (${f.j?.host?.name})`);
+ /* Und zurueck auf DogFather, damit der Rest der Pruefung von ihm
+ aus weiterlaeuft. */
+ await roh(CREW, "/workspace/api/reaktion/stand", { method: "POST", keks: k.hand, rumpf: { stand: "zu" } });
+ await roh(CREW, "/workspace/api/reaktion", {
+ method: "PUT", keks: k.admin, rumpf: { titel: "Wir schauen den Trailer", video: "dQw4w9WgXcQ" },
+ });
+ await roh(CREW, "/workspace/api/reaktion/stand", { method: "POST", keks: k.admin, rumpf: { stand: "live" } });
}
/* ---- Ohne Video kein Live ---- */
@@ -274,11 +341,25 @@ melde("=== 5. Dabei sein ===");
ok(Array.isArray(a.j?.zuschauer) && a.j.zuschauer.length === 3,
"DogFather sieht, WER dabei ist");
+ const h = await roh(CREW, "/workspace/api/reaktion", { keks: k.hand });
+ ok(Array.isArray(h.j?.zuschauer) && h.j.zuschauer.length === 3,
+ "die rechte Hand sieht es auch -- sie fuehrt mit");
+
const z = await roh(CREW, "/workspace/api/reaktion", { keks: k.gast });
ok(z.j?.zuschauer === null,
"ein Zuschauer bekommt die Namensliste NICHT");
ok(z.j?.dabei === 3, `die Zahl sieht er aber (${z.j?.dabei})`);
+ /* „NUR DIE ZWEI SOLLEN ALLES SEHEN." Die Moderation stand hier
+ frueher mit drin, weil sie Beitraege wegnehmen darf -- das ist
+ eine Vermischung zweier Dinge, die nichts miteinander zu tun
+ haben. Wegnehmen duerfen sie weiter (Abschnitt 7). */
+ for (const rolle of ["linke", "modi"]) {
+ const m = await roh(CREW, "/workspace/api/reaktion", { keks: k[rolle] });
+ ok(m.j?.zuschauer === null,
+ `${rolle}: moderiert, bekommt die Namensliste aber nicht`);
+ }
+
/* Jeder Dabeisitzende hat einen Kameraplatz bekommen -- es sind
erst drei, und zwoelf waeren erlaubt. */
ok(a.j.zuschauer.every((x) => x.sicht === true),
@@ -343,6 +424,10 @@ melde("=== 6. Ein bis zwei Gaeste ===");
method: "POST", keks: k.gast, rumpf: { person_id: IDS.modi },
});
ok(f.code === 403, `ein Gast holt niemanden dazu (${f.code})`);
+ const f2 = await roh(CREW, "/workspace/api/reaktion/gast", {
+ method: "POST", keks: k.modi, rumpf: { person_id: IDS.linke },
+ });
+ ok(f2.code === 403, `ein Modi auch nicht (${f2.code})`);
const w = await roh(CREW, `/workspace/api/reaktion/gast/${IDS.gast2}`, {
method: "DELETE", keks: k.admin,
diff --git a/server/workspace-reaktion.js b/server/workspace-reaktion.js
index 13352772..4fb429b8 100644
--- a/server/workspace-reaktion.js
+++ b/server/workspace-reaktion.js
@@ -52,9 +52,25 @@ import {
export const reaktionRouter = express.Router();
-/** Wer die Sendung fuehrt. */
-const HOST_ROLLEN = new Set(["admin"]);
-/** Wer Beitraege aus dem Chat nehmen darf. */
+/** Wer die Sendung fuehrt: DogFather und die rechte Hand.
+ *
+ * Filipe am 28.09.2026: „die rolle dogfather und vanvan sollen alles
+ * sehen und bereit machen auch vorher. nur die zwei sollen alles
+ * sehen, vorbereiten und einschakten koennen."
+ *
+ * Das ist eine AUFGABE und kein Rang -- wer die Sendung fuehrt,
+ * bedient die Technik. Beide duerfen dasselbe; es gibt hier keinen
+ * Ersten und keinen Zweiten. */
+const HOST_ROLLEN = new Set(["admin", "hand"]);
+
+/** Wer Beitraege aus dem Chat nehmen darf.
+ *
+ * BLEIBT GROESSER ALS HOST_ROLLEN, und das ist Absicht: Einen
+ * Beitrag wegzunehmen ist weder „alles sehen" noch „vorbereiten"
+ * noch „einschalten" -- es ist dasselbe, was die Modis im Treff
+ * ohnehin tun. Wer moderiert, bekommt deshalb aber AUCH NICHT die
+ * Liste derer, die zusehen; die beiden Dinge hingen vorher
+ * zusammen, ohne dass es einen Grund dafuer gab. */
const MODERIERT = new Set(["admin", "hand", "linke", "modi"]);
export const REAKTION_HOST_ROLLEN = HOST_ROLLEN;
@@ -183,8 +199,15 @@ function lage(person) {
nie an (sonst rufen zwei gleichzeitig an, und beide
scheitern -- „glare"). Ohne die Liste kaeme das Bild eines
Gastes deshalb bei keinem Zuschauer an. Zwei fuer sich richtige
- Regeln, die zusammen ein Loch ergeben. */
- zuschauer: (istHost || binGast || MODERIERT.has(person.rolle))
+ Regeln, die zusammen ein Loch ergeben.
+
+ DIE MODERATION STAND HIER AUCH -- SEIT DEM 28.09. NICHT MEHR.
+ Filipe: „nur die zwei sollen alles sehen." Wer einen Beitrag
+ wegnehmen darf, muss deshalb nicht wissen, wer im Saal sitzt;
+ das war eine Vermischung zweier Dinge, die nichts miteinander
+ zu tun haben. Moderiert wird weiter -- im Chat sieht man
+ ohnehin, wer da ist. */
+ zuschauer: (istHost || binGast)
? dabei.map((d) => ({ id: d.id, name: d.name, rolle: d.rolle, sicht: !!d.sicht }))
: null,
ich: {
@@ -240,9 +263,23 @@ reaktionRouter.put("/workspace/api/reaktion", nurHost, express.json({ limit: "32
return res.status(400).json({ fehler: "beginn_vorbei" });
}
+ /* HOST_ID WIRD BEIM EINSTELLEN NUR GESETZT, WENN NOCH NIEMAND
+ DASTEHT (28.09.2026).
+
+ Vorher stand hier, wer zuletzt etwas eingestellt hat. Mit
+ zwei Leuten, die vorbereiten duerfen, ist das eine Falle:
+ VanVan richtet am Nachmittag alles ein, Filipe drueckt
+ abends auf Sendung -- und im Bild stuende VanVans Name,
+ waehrend Filipe redet.
+
+ Wer die Sendung FUEHRT, entscheidet sich beim Einschalten
+ (siehe /stand). Hier wird nur der leere Platz gefuellt, damit
+ die Ankuendigung „Als Naechstes" jemanden nennen kann. */
+ const bisher = sendung();
db().prepare(`UPDATE reaktion SET titel = ?, video = ?, vorschau = ?,
beginnt_am = ?, host_id = ?, geaendert = ? WHERE id = 1`)
- .run(titel, video, vorschau, beginn, req.person.id, jetzt());
+ .run(titel, video, vorschau, beginn,
+ bisher.host_id || req.person.id, jetzt());
const l = lage(req.person);
melden("reaktion", { was: "eingestellt", lage: oeffentlich(l) });
@@ -272,6 +309,10 @@ reaktionRouter.post("/workspace/api/reaktion/stand", nurHost, express.json({ lim
felder.gestartet_am = jetzt();
felder.laeuft = 1;
felder.sekunde = 0;
+ /* WER EINSCHALTET, IST IM BILD. Seine Kamera bekommt den
+ Platz des Hosts -- und sein Name steht daran. Der andere
+ kann jederzeit als Gast dazukommen. */
+ felder.host_id = req.person.id;
}
if (neu === "zu" && s.stand === "live") {
/* WAS VORBEI IST, WANDERT IN DEN VERLAUF -- und der Live-Chat
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index c41fd152..66ea1b7e 100644
--- a/workspace/anruf-probe.html
+++ b/workspace/anruf-probe.html
@@ -16,8 +16,8 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
-
-
+
+
-
+