Das Regie-Tor: jeder sieht es, nur die zwei kommen rein -- und Farbe für die Reihen

Filipe, zum Bildschirmfoto des Foyers: „unter den [Reihen] fehlt eine
kachel, die viel kraesser und spezieller aussehen soll, wo nur
dogfather oder vanvan reinkommen. mit ihren zugangscodes für die seite.
und das soll die regie kachel sein. die muss wirklich ultra krass sein.
komplett crazy. die anderen kacheln aber auch gerne farbiger machen und
nicht so kaal und dunkel."

Auf die Rückfrage, wie fest das Schloss sein soll: „punkt 2 aber die
soll jeder sehen aber nur vanvan und dogfather sollen da rein kommen
bitte."

Haus: Team Dogi (crew.dogfather-universe.com). Die Agentur ist nicht
berührt -- pruef-haus-trennung 107/0.

DAS TOR

Eine vierte Kachel über die GANZE BREITE unter den drei Reihen. Das ist
die stärkste Aussage, die ein Raster treffen kann, und sie kostet keine
einzige Farbe. Ein Licht läuft in sieben Sekunden darüber -- flach und
schmal, wie der Schein einer Lampe über einem Mischpult. Kein Blinken:
dasselbe Signal mit doppelter Belastung für die Augen, und diese Seite
steht manchmal eine Stunde offen. prefers-reduced-motion bekommt den
Schein stehend, nicht gar keinen -- der Zustand muss auch dann zu
erkennen sein.

Drei Zustände, und jeder sieht anders aus: verschlossen rot mit
geschlossenem Bügel, aufgeschlossen grün mit aufspringendem Bügel, und
für alle anderen gedämpftes Grau ohne Lauflicht, mit „nicht erlaubt"
schon am Mauszeiger.

ZWEI SCHLÖSSER HINTEREINANDER, UND NUR EINES IST GEHEIM

(1) DIE ROLLE, und zwar VOR dem Code -- ohne ihn anzusehen. Das ist
    wichtiger, als es aussieht: Sonst könnte irgendwer im Haus mit
    Rateversuchen die `versuche`-Bremse für seine eigene IP vollaufen
    lassen und sich damit von der ANMELDUNG aussperren; beide zählen in
    derselben Tabelle. Geprüft mit Gegenprobe: Ein Gast schickt den
    RICHTIGEN Admin-Code, kommt nicht durch, und die Versuchszahl
    bleibt bei 0.
(2) DER CODE, geprüft mit `codeGeprueft()` -- neu in workspace.js,
    neben der Anmeldung und mit deren Rechenvorschrift, deren Vergleich
    in konstanter Zeit und deren Bremse. Ein zweiter Codevergleich in
    einem anderen Modul wäre der, der beim nächsten Umbau der
    scrypt-Parameter stehen bleibt.

Und er prüft GENAU DIESE PERSON. Die Anmeldung geht alle Personen einer
Rolle durch -- dort ist der Code die Kennung. Hier wäre das falsch:
VanVans Code öffnete DogFathers Tür, und im Protokoll stünde, ER sei
hineingegangen.

WAS DIE TÜR LEISTET UND WAS NICHT -- UND DASS ES DASTEHT

Filipes „Punkt 2" heißt: Der Code öffnet die Tür, danach ist die Regie
offen wie bisher. Die Routen der Sendung prüfen weiterhin nur die Rolle.
Das ist die bewusste Wahl und keine vergessene Stelle -- eine Sperre,
die mitten in einer Übertragung zuschnappen kann, richtet mehr Schaden
an, als sie verhindert.

Damit das niemand überschätzt, steht es als Satz IM FENSTER, nicht nur
im Quelltext: „Das hält einen neugierigen Blick auf, nicht jemanden, der
an deinem offenen Rechner sitzt." Eine Sicherung, die stärker aussieht,
als sie ist, ist schlechter als gar keine.

FARBE -- ABER NICHT AUF DER FLÄCHE

Filipe hatte recht, und der Grund war meiner: Beim Umbau auf das
Hausmaterial heute Vormittag habe ich die Farbe mit herausgenommen, weil
die alte Fassung sie auf der FLÄCHE trug -- und genau das machte den
Text schlecht lesbar. Richtig ist nicht „keine Farbe", sondern Farbe,
wo kein Text steht:

  * Jede Reihe hat ihren Ton (`--ton`, derselbe Griff, über den
    module.css das Kantenlicht legt): Bernstein für das, was ansteht
    -- dieselbe Farbe wie „überfällig" --, Blau für die Sendung, rot
    sobald sie läuft, Grün für das, was hereinkommt: dieselbe Farbe,
    die ein angenommener Vorschlag trägt. Die Farben SAGEN etwas.
  * Ein Band im Kopf jeder Tafel, die Überschrift in ihrem Ton, die
    Schilder passend. Vorher war jedes Schild blau, egal in welcher
    Reihe es stand -- zwei Farben nebeneinander, die nichts
    voneinander wussten.
  * Ein Streifen am Zeilenanfang statt eines eingefärbten Kastens.
    Drei Pixel an der Kante sagen dasselbe, und der Text steht
    weiterhin auf dem Grund, auf dem er gemessen wurde.

Die Kachel selbst trägt dieselbe Silhouette und dasselbe deckende
Material wie jede Karte im Haus (`.regie-tor` steht in der Modulliste in
module.css und in der Materialliste in start.css). „Krass" heißt hier
nicht „anders als das Haus" -- genau das stand heute Vormittag schon
einmal in foyer.css und war ein Fehler.

PROTOKOLLWÖRTER, DIE MAN LESEN KANN

`personen.js` baut den Anzeigetext aus dem Schlüssel: Unterstriche
werden Leerzeichen, nur der erste Buchstabe wird groß. Aus
`regie_code_falsch` wäre auf dem Bildschirm „Regie code falsch"
geworden. Die Regel, die daraus folgt: Hauptwort plus Mittelwort, nie
zwei Hauptwörter. Jetzt `regie_aufgeschlossen`, `regie_abgeschlossen`,
`regie_verweigert`, `regie_unbefugt`. Und das Detail war ein
ISO-Zeitstempel mitten in einer Zeile, die ein Mensch überfliegt --
jetzt steht dort „12 Stunden".

GEMESSEN

  pruef-reaktion          571, 0 Fehler (vorher 538) -- Abschnitt 21
  mess-foyer               96, 0 Fehler (vorher 70), 12 Bildschirmfotos
  pruef-handy-teamdogi    263 Seitenaufrufe, 0 Befunde
  pruef-breiten            23 auf 45 Seiten und fünf Breiten
  pruef-struktur          102 · pruef-css-klassen 37 · pruef-lesbarkeit 14
  pruef-bewegung            9 · pruef-tippziele 13 · pruef-deutsche-texte 12
  pruef-crew-adresse      173 · pruef-haus-trennung 107
  pruef-community-sicht    10 · pruef-sackgassen 14
  alle 0 Fehler

UND EINE LÜCKE, DIE DIE EIGENE MESSUNG GEFUNDEN HAT

Der Vorhang mit dem Codefeld schließt auf Esc und auf einen Druck
daneben, und der Finger steht im Feld -- für jemanden ohne Maus war er
trotzdem eine Falle: Die Tabulatortaste lief durch die Knöpfe DAHINTER
weiter, sichtbar war aber das Codefeld. Man tippt auf A und bekommt B,
nur eben mit der Tastatur.

Der erste Riegel legte `#foyer` still -- und die neue Messung fiel
sofort darüber: Die KOPFLEISTE steht außerhalb davon, der Fokus lief
weiter nach „Abmelden". Jetzt wird alles neben dem Vorhang stillgelegt,
ABGELEITET statt aufgezählt (`body.children`), und beim Schließen genau
das wieder freigegeben, was ich selbst gesetzt habe.

Und die Messung selbst war beim ersten Entwurf zu streng: Sie verlangte
„der Fokus bleibt IMMER im Fenster" und wurde rot, obwohl die Sperre
tadellos arbeitete -- am Ende des Tabulatorkreises gibt der Browser den
Fokus an seine eigene Leiste ab, im Dokument steht dann `body`. `body`
ist kein Bedienelement. Gefragt ist jetzt das Richtige: Wird je ein
BEDIENELEMENT außerhalb erreicht? Die Gegenprobe nennt es beim Namen
(`DRAUSSEN:zurueck`).

mess-foyer misst beide Hälften von Filipes Satz: dass ein Gast das Tor
SIEHT (und ein Druck ihm trotzdem kein Codefeld öffnet) und dass nur die
zwei HINEINKOMMEN. Dazu der ganze Weg im Browser: falscher Code
abgewiesen und Feld geleert, richtiger Code führt in den Saal, die
Freigabe gilt in einem neuen Fenster -- und VanVans Tür ist trotzdem
noch zu.

EIN FEHLALARM IN DER EIGENEN MESSUNG, BEHOBEN

mess-foyer suchte zuerst das WORT „Warteschlange" im Dokument eines
Gastes und fand es -- im unsichtbaren Gerüst der linken Reihe, wo es als
Überschrift steht. Zwei Gründe, warum das falsch war: Eine Beschriftung
ist keine Auskunft, und dass es eine Warteschlange GIBT, steht seit
heute im Regie-Tor, das jeder sieht. Eine Messung, die genau das als
Leck zählt, widerspricht dem Entwurf -- und sie hätte bei jedem Lauf
angeschlagen. Gefragt ist das Schärfere: Kommen DATEN an? Jetzt werden
Titel und Videokennungen geprüft, und dass keine einzige Planzeile
gebaut wurde.

NICHT BEHOBEN, WEIL NICHT MEINS: pruef-meldungen bleibt rot (28
Kennungen ohne Satz, 3 Rohanzeigen) -- gemessen im worktree auf dem
letzten Commit schon vorher, Zeichen für Zeichen dieselbe Liste. Meine
Arbeit hat 2 Kennungen und 2 Sätze ergänzt (174 -> 176, 155 -> 157), die
Zahlen gehen genau gleich hoch. Die Dateien gehören überwiegend der
Agentur; das gehört in einen eigenen Durchgang.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-08 12:13:22 +02:00
co-authored by Claude Opus 5
parent fdadd5d573
commit 0d52c4dc05
58 changed files with 2034 additions and 724 deletions
+215 -4
View File
@@ -25,6 +25,10 @@
* Und geht der ganze Weg durch: Gast schickt ein Video,
DogFather sieht es MIT Namen, nimmt es -- und es steht links
im Plan.
* Steht das Regie-Tor bei JEDEM da (Filipes ausdruecklicher
Wunsch) -- und kommt trotzdem nur durch, wer den richtigen
Code hat? Gemessen werden alle vier Lagen: Gast (kein Feld),
Host verschlossen, Host mit falschem Code, Host mit richtigem.
ZU JEDER MESSUNG, DIE "in Ordnung" SAGEN KANN, GEHOERT EINE
GEGENPROBE. Am Ende wird jede der tragenden Messungen absichtlich
@@ -347,6 +351,29 @@ const massLesen = (seite) => seite.evaluate(() => {
};
});
/** Was sagt das Regie-Tor? */
const torLesen = (seite) => seite.evaluate(() => {
const tor = document.getElementById('regie-tor');
if (!tor) return null;
const r = tor.getBoundingClientRect();
const t = (k) => tor.querySelector(k)?.textContent.trim() || '';
return {
da: !!tor.offsetParent,
darf: tor.dataset.darf,
offen: tor.dataset.offen,
titel: t('.regie-tor__titel'),
satz: t('.regie-tor__satz'),
marke: t('.regie-tor__marke'),
breit: Math.round(r.width),
hoch: Math.round(r.height),
oben: Math.round(r.top),
/* Die Farbe, die das Licht traegt -- sie ist die Aussage ueber
den Zustand und nicht nur Zierde. */
ton: getComputedStyle(tor).getPropertyValue('--ton').trim(),
vorhangDa: !document.getElementById('regie-vorhang').hidden,
};
});
/** Was steht in der Mitte? */
const mitteLesen = (seite) => seite.evaluate(() => {
const t = (id) => document.getElementById(id)?.textContent.trim() || "";
@@ -593,13 +620,187 @@ console.log("\n=== 6. Anraten, sehen, nehmen ===");
}
/* =====================================================================
7. DIE GEGENPROBEN
7. DAS REGIE-TOR
Filipe: „die soll jeder sehen aber nur vanvan und dogfather sollen
da rein kommen bitte." Beide Haelften werden gemessen -- dass sie
jeder SIEHT und dass nur die zwei HINEINKOMMEN. Eine davon allein
waere keine Aussage.
===================================================================== */
console.log("\n=== 7. Das Regie-Tor ===");
{
const gast = await alsWer("gast", 1440, 900);
const t = await torLesen(gast.seite);
ok(!!t && t.da, "der Gast SIEHT das Tor");
ok(t?.darf === "nein", `fuer ihn steht darf=nein (${t?.darf})`);
ok(t?.marke === "Nur ihr zwei", `das Schild sagt es (${t?.marke})`);
ok(/nicht hinein|T\u00fcr zu/i.test(t?.satz || ""),
`und der Satz auch (${t?.satz})`);
/* ==== ES DARF KEINE DATEN VERRATEN ============================
Der erste Entwurf suchte hier auch das WORT „Warteschlange" --
und fand es, im unsichtbaren Geruest der linken Reihe, wo es als
Ueberschrift steht. Das war ein Fehlalarm aus zwei Gruenden:
* Eine Beschriftung ist keine Auskunft. Dass es eine
Warteschlange GIBT, steht ohnehin im Regie-Tor, und das
sieht seit heute jeder -- auf Filipes ausdruecklichen
Wunsch. Eine Messung, die genau dieses Wort als Leck
zaehlt, widerspricht dem Entwurf.
* Und sie haette bei jedem Lauf angeschlagen. Eine Warnung,
die immer kommt, nimmt irgendwann die echte mit.
Gefragt ist das Schaerfere: Kommen DATEN an? Titel und
Videokennungen aus der Warteschlange duerfen im Dokument eines
Gastes nicht vorkommen -- und gebaute Zeilen schon gar nicht. */
const leck = await leckLesen(gast.seite,
[...liste0.map((z) => z.titel), ...liste0.map((z) => z.video)]);
ok(leck.length === 0, "das Tor verraet keine Daten aus der Regie"
+ (leck.length ? " -> " + leck.join(", ") : ""));
const zeilen = await gast.seite.evaluate(() => ({
plan: document.querySelectorAll("#plan-liste .plan-zeile").length,
leerzeile: document.querySelectorAll("#plan-liste .plan-leer").length,
}));
ok(zeilen.plan === 0 && zeilen.leerzeile === 0,
`und es ist auch keine Planzeile gebaut worden `
+ `(${zeilen.plan} Zeilen, ${zeilen.leerzeile} Leerhinweise)`);
/* EIN DRUCK OEFFNET IHM KEIN FELD. Sonst waere eine Kachel, die
jeder sieht, eine Ratestelle fuer jeden im Haus. */
await gast.seite.click("#regie-tor");
await gast.seite.waitForTimeout(150);
const danach = await torLesen(gast.seite);
ok(danach?.vorhangDa === false, "ein Druck oeffnet ihm kein Codefeld");
await bild(gast.seite, "10-tor-gast");
await gast.kontext.close();
}
{
const chef = await alsWer("admin", 1440, 900);
const t = await torLesen(chef.seite);
ok(t?.darf === "ja", `DogFather darf (${t?.darf})`);
ok(t?.offen === "nein", `und es ist zu (${t?.offen})`);
ok(t?.marke === "Verschlossen", `das Schild sagt es (${t?.marke})`);
const r = await reihenLesen(chef.seite);
ok(!!t && t.breit > r.saal.breit,
`das Tor ist breiter als die breiteste Reihe (${t?.breit} zu ${r.saal?.breit} px)`);
ok(!!t && !!r.saal && t.oben > r.saal.oben,
`und es steht UNTER den Reihen (${t?.oben} nach ${r.saal?.oben} px)`);
/* ---- Falscher Code ---- */
await chef.seite.click("#regie-tor");
await chef.seite.waitForSelector("#regie-vorhang:not([hidden])", { timeout: 5000 });
ok(true, "ein Druck oeffnet ihm das Codefeld");
await bild(chef.seite, "11-tor-vorhang");
/* ==== DIE TABULATORTASTE DARF NICHT HINTER DEN VORHANG ========
Esc schliesst, ein Druck daneben schliesst, der Finger steht im
Feld -- und trotzdem war der Vorhang fuer jemanden ohne Maus
eine Falle: Die Tabulatortaste lief durch die Knoepfe DAHINTER
weiter, sichtbar war aber das Codefeld. Gemessen wird deshalb
nicht, ob `inert` gesetzt ist, sondern wo der Fokus LANDET. */
{
/* `body` IST KEIN BEDIENELEMENT, und das ist hier der Unterschied
zwischen einem Befund und einem Fehlalarm. Am Ende des
Tabulatorkreises gibt der Browser den Fokus an die Leiste des
Browsers ab; im Dokument steht dann `document.body`. Mein
erster Entwurf verlangte „immer drin" und wurde deshalb rot,
obwohl die Sperre tadellos arbeitete -- gemeldet wurde
„drin, body", und kein einziges Bedienelement ausserhalb.
Gefragt ist also: Wird je ein BEDIENELEMENT ausserhalb des
Fensters erreicht? Deshalb sagt die Messung auch, WELCHES --
ein Befund, der nur „draussen" meldet, schickt einen suchen. */
const woher = () => chef.seite.evaluate(() => {
const e = document.activeElement;
if (!e || e === document.body || e === document.documentElement) return "body";
return e.closest("#regie-vorhang")
? "drin"
: "DRAUSSEN:" + (e.id || e.className || e.tagName);
});
const wo = [];
for (let i = 0; i < 6; i++) { await chef.seite.keyboard.press("Tab"); wo.push(await woher()); }
ok(wo.includes("drin") && !wo.some((x) => x.startsWith("DRAUSSEN")),
`kein Bedienelement hinter dem Vorhang ist erreichbar `
+ `(6x Tab: ${[...new Set(wo)].join(", ")})`);
/* GEGENPROBE: Ohne die Stilllegung MUSS ein Element dahinter
auftauchen. Sonst bewiese die Zeile darueber nur, dass sechsmal
Tab irgendwo landet -- und nicht, dass die Sperre es ist. */
await chef.seite.evaluate(() => {
for (const e of document.body.children) e.inert = false;
});
const raus = [];
for (let i = 0; i < 8; i++) { await chef.seite.keyboard.press("Tab"); raus.push(await woher()); }
ok(raus.some((x) => x.startsWith("DRAUSSEN")),
`Gegenprobe: ohne die Sperre wird eines erreicht `
+ `(${raus.find((x) => x.startsWith("DRAUSSEN")) || [...new Set(raus)].join(", ")})`);
await chef.seite.evaluate(() => {
for (const e of document.body.children) {
if (e.id !== "regie-vorhang") e.inert = true;
}
});
}
await chef.seite.fill("#regie-code", "CODE-XXX-999999");
await chef.seite.click("#regie-los");
await chef.seite.waitForSelector("#regie-fehler:not([hidden])", { timeout: 8000 });
const meldung = await chef.seite.textContent("#regie-fehler");
ok(/Code stimmt nicht/.test(meldung || ""),
`ein falscher Code wird abgewiesen, in ganzen Saetzen (${meldung})`);
const geleert = await chef.seite.inputValue("#regie-code");
ok(geleert === "", "und das Feld ist danach leer");
const nochZu = await torLesen(chef.seite);
ok(nochZu?.offen === "nein", "das Tor bleibt zu");
/* ---- Richtiger Code ---- */
await chef.seite.fill("#regie-code", CODES.admin);
await Promise.all([
chef.seite.waitForURL("**/reaktion.html", { timeout: 15000 }),
chef.seite.click("#regie-los"),
]);
ok(/reaktion\.html/.test(chef.seite.url()),
`der richtige Code fuehrt in den Saal (${chef.seite.url().split("/").pop()})`);
await chef.kontext.close();
}
{
/* ---- Und danach ist es offen, auch in einem neuen Fenster ----
Das ist der Punkt der ganzen Freigabe: Sie haengt an der Person
und nicht an dem Reiter, in dem getippt wurde. */
const chef = await alsWer("admin", 1440, 900);
const t = await torLesen(chef.seite);
ok(t?.offen === "ja", `das Tor steht offen (${t?.offen})`);
ok(t?.marke === "Offen", `das Schild sagt es (${t?.marke})`);
ok(/Offen bis/.test(t?.satz || ""), `und bis wann (${t?.satz})`);
await bild(chef.seite, "12-tor-offen");
/* VanVan hat ihre eigene Tuer. Waere die Freigabe an der Rolle
festgemacht statt an der Person, stuende sie jetzt auch bei ihr
offen -- und im Protokoll staende, SIE sei hineingegangen. */
const van = await alsWer("hand", 1440, 900);
const tv = await torLesen(van.seite);
ok(tv?.darf === "ja", `VanVan darf auch (${tv?.darf})`);
ok(tv?.offen === "nein",
`aber ihre Tuer ist ihre eigene und noch zu (${tv?.offen})`);
await van.kontext.close();
/* ---- Wieder abschliessen ---- */
await chef.seite.evaluate(() => fetch('/workspace/api/reaktion/regie',
{ method: 'DELETE', credentials: 'same-origin' }));
await chef.seite.reload({ waitUntil: "networkidle" });
await chef.seite.waitForTimeout(400);
const zu = await torLesen(chef.seite);
ok(zu?.offen === "nein", `abschliessen geht auch wieder (${zu?.offen})`);
await chef.kontext.close();
}
/* =====================================================================
8. DIE GEGENPROBEN
Fuenf Messungen oben koennen "in Ordnung" sagen. Hier wird jede
absichtlich kaputtgemacht -- schlaegt sie nicht an, ist sie
wertlos, und alle gruenen Haken davor beweisen nichts.
===================================================================== */
console.log("\n=== 7. Gegenproben ===");
console.log("\n=== 8. Gegenproben ===");
{
const chef = await alsWer("admin", 390, 844);
@@ -655,6 +856,16 @@ console.log("\n=== 7. Gegenproben ===");
await gast.seite.waitForTimeout(120);
const sichtbar = await reihenLesen(gast.seite);
ok(sichtbar.plan !== null, "Gegenprobe: eine aufgedeckte Reihe gilt als sichtbar");
/* (g) Und der Tor-Leser muss ein verschwundenes Tor auch
verschwunden nennen -- sonst waere „der Gast sieht es" oben nur
die Folge davon, dass er immer „da" sagt. */
await gast.seite.evaluate(() => {
document.getElementById("regie-tor").style.display = "none";
});
await gast.seite.waitForTimeout(120);
const weg = await torLesen(gast.seite);
ok(weg?.da === false, "Gegenprobe: ein ausgeblendetes Tor gilt als weg");
await gast.kontext.close();
}
@@ -669,8 +880,8 @@ try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
einzige Messung, waere "0 Fehler" das beste Ergebnis ueberhaupt --
und voellig wertlos. */
console.log(`\n${geprueft} Messungen, ${fehler} Fehler`);
if (geprueft < 40) {
console.log(`NICHT NACHSEHBAR: nur ${geprueft} Messungen gelaufen, erwartet mindestens 40.`);
if (geprueft < 55) {
console.log(`NICHT NACHSEHBAR: nur ${geprueft} Messungen gelaufen, erwartet mindestens 55.`);
process.exit(3);
}
if (fehler) {