Nichts wird mehr nach links oder rechts geschoben

Filipe, 28.09.2026: "ich will auch nicht dass man sachen nach links
und rechts schieben muss. perfektion das untereinander. ich will
niemals irgendwas nach links oder rechts swippen muessen." Dazu ein
Bildschirmfoto der Tafel "Gestaltung" mit waagerechter Rollleiste.

GEMESSEN, NICHT GERATEN

Ein grep nach `overflow-x` findet nur die absichtlichen Roller. Er
findet nicht die Stelle, an der ein Inhalt breiter ist als sein
Kasten und der Browser von sich aus eine Rollleiste anhaengt -- und
genau das war auf dem Bild zu sehen. `server/mess-quer.mjs` geht
deshalb im echten Browser jedes Element durch, auf fuenf
Bildschirmgroessen, in allen sieben Registern, bei offener und
zugeklappter Regie und in allen fuenf Anordnungen.

Erster Lauf: 78 Stellen. Davon waren 54 KEINE -- `overflow-x: hidden`
ist abgeschnittener Text, dort laesst sich nichts schieben. Die
Messung trennt das jetzt; wer es mitzaehlt, findet die echten nicht
mehr. Uebrig blieben vier Ursachen:

  1. DIE REGISTER rollten absichtlich waagerecht. Auf 412 px standen
     von 605 px Registern 193 rechts ausserhalb -- dass es
     "Gestaltung" und "Spenden" gibt, erfuhr man nur beim Wischen auf
     Verdacht. Sie brechen jetzt um. Das kostet oben rund 45 Pixel
     und bringt Gewissheit dafuer.

  2. `.stufen` STAND ZWEIMAL IN reaktion.css -- einmal fuer die
     Stufenleiter der Spenden (`display: grid`), 600 Zeilen spaeter
     fuer die Chat-Leiste Offen/Team/Zu (`display: flex`). Die
     spaetere gewinnt: Die Stufenleiter stellte ihre drei Stufen
     NEBENEINANDER, 572 Pixel in einer 327 Pixel schmalen Spalte.
     Das ist die Rollleiste auf Filipes Bild. Die Chat-Leiste heisst
     jetzt `.chatstufen` / `.chatstufe`.
     Dritter Fall dieser Art nach `.tafel` und `.stufe-knopf`.

  3. DIE TAFELN KONNTEN NICHT SCHRUMPFEN. Ein Gitterfeld hat von
     sich aus `min-width: auto` und besteht auf seinem unteilbarsten
     Inhalt. Mit `minmax(0, 1fr)` und `min-width: 0` bricht jetzt
     alles um, statt hinauszulaufen.

  4. DIE TRANSPORTLEISTE ragte am Handy 76 Pixel ueber beide Kanten
     ("Naechstes" war nur halb zu sehen). Zwei Gruende: `.transport__teil`
     hatte kein `flex-wrap` (die mittlere Gruppe schon -- zwei Regeln
     fuer dieselbe Sache, eine vergessen), und im Umschalter steht
     ein ganzer Videotitel, der als Flex-Kind auf seiner vollen
     Breite bestand.

NEBENBEFUND: EINE FESTE ZAHL VON GESTERN

Der Saal stand auf `height: calc(100dvh - 69px)` -- 69 war einmal
die gemessene Hoehe der Kopfleiste. Sie ist 73 geworden, und der
Saal endete damit 4 Pixel unter dem Fensterrand: Der Play-Knopf war
nur mit Scrollen zu erreichen. Die Antwort ist keine neue Zahl,
sondern eine Regel, die misst -- der Koerper ist jetzt eine Spalte,
die Kopfleiste nimmt, was sie braucht, der Saal bekommt den Rest.
(Dabei noch ein Spezifitaetsunfall: `.reaktion-seite` (0,1,0)
verliert gegen `body.start` (0,1,1) aus start.css.)

NEUE WACHEN

  - `mess-quer.mjs` mit Gegenprobe (ohne die Reparaturen findet sie
    47 px, mit ihnen nichts).
  - pruef-reaktion: kein absichtliches `overflow-x: auto` mehr; und
    zwei Grundregeln derselben Klasse mit verschiedenem `display`
    sind ein Namensstreit. Der bisherige Waechter verglich nur
    ZWISCHEN Stilvorlagen -- `.stufen` stand zweimal in DERSELBEN.
  - pruef-css-klassen: jede Klammer hat ihr Gegenstueck. Beim
    Umschreiben blieb heute das Ende einer Regel ohne ihren Anfang
    stehen; der Browser wirft die Zeile weg und schliesst dafuer den
    naechsten @media-Block zu frueh. Drei Befunde sahen daraufhin wie
    Programmfehler aus.

MESSUNG NACHGEZOGEN

`mess-reaktion` stammte noch aus der Zeit vor "Regie links" und
"drei Kacheln" und meldete drei Dinge, die keine Fehler waren --
gemessen gegen den Stand von HEAD: identisch rot. Sie misst ausserdem
seit heute mit FINGER statt Mauszeiger; ohne `hasTouch` greift keine
einzige Regel aus `@media (pointer: coarse)`, und dort stehen alle
44-Pixel-Beruehrziele des Hauses.

OFFEN UND AUFGESCHRIEBEN

Am Handy bleibt bei offener Regie vom Bild nichts (0 px). Der Mangel
besteht seit dem Umbau "Regie links"; drei Versuche, ihn heute zu
loesen, haben Knoepfe verdeckt -- und ein verdeckter Knopf ist
schlimmer als ein kleines Bild. Die Versuche und der richtige Weg
(Tempo-Gruppe hinter einen Knopf, wie "Aa" im Chat) stehen in
reaktion.css. `mess-reaktion` meldet ihn als dritten Ausgang: nicht
als Fehler und nicht als bestanden.

GEPRUEFT
  pruef-reaktion     384 / 0   (vorher 379)
  pruef-spenden      172 / 0
  pruef-buehne        36 / 0
  pruef-css-klassen, -tippziele, -struktur, -handy: ohne Befund
  mess-quer          EXIT 0 -- 28 Stellen, fuenf Groessen, nichts rollt
  mess-reaktion      EXIT 0, 1 benannter offener Punkt
  mess-buehne        EXIT 0
This commit is contained in:
2026-09-29 02:34:09 +02:00
parent 813d2e85a1
commit a9e0834cfb
50 changed files with 2669 additions and 1523 deletions
+248 -43
View File
@@ -204,6 +204,14 @@ const browser = await pw.chromium.launch({
async function alsWer(rolle, breite, hoehe) {
const kontext = await browser.newContext({
viewport: { width: breite, height: hoehe },
/* MIT FINGER, WENN ES EIN HANDY IST.
Ohne `hasTouch` meldet der Browser `pointer: fine` -- und
damit greift KEINE der Regeln aus `@media (pointer: coarse)`.
Dort stehen im ganzen Haus die 44-Pixel-Beruehrziele und seit
dem 28.09. auch das Ausblenden der Tastenkuerzel. Eine
Handy-Messung mit Mauszeiger misst eine Seite, die auf keinem
Handy so aussieht -- und meldet Hoehen, die dort andere sind. */
hasTouch: breite <= 860,
ignoreHTTPSErrors: true,
permissions: ["camera", "microphone"],
});
@@ -357,6 +365,11 @@ console.log(` -> ${zaehlt} von ${Object.keys(beiZuschauer).length} Kameras kom
ein Video mit Ton nicht von allein startet. Beide Male sahen die
Bildschirmfotos plausibel aus. */
let ausgang = 0;
/* BEKANNTE, AUFGESCHRIEBENE OFFENE PUNKTE.
Sie sind keine Fehler dieses Laufs und kein bestandener Test --
sie sind der dritte Ausgang: Mangel bekannt, Weg beschrieben,
noch nicht gegangen. Am Ende stehen sie gesondert. */
const offenePunkte = [];
/* LAEUFT DAS VIDEO? Ein Zuschauer hat keine Bedienung -- startet es
nicht von selbst, sitzt er eine Stunde vor einem Standbild und
kann nichts dagegen tun. Gemessen war genau das der Fall. */
@@ -396,7 +409,7 @@ await host.seite.evaluate(() => {
});
await host.seite.waitForTimeout(900);
/* ---- Der Regieplatz: links, rechts und die Leiste unten ----------
/* ---- Der Regieplatz: drei Kacheln und die Leiste unten ----------
Filipe: „kann das nicht bissl aufgeteilt sein auf links und rechts
und eine barre unten in der mitte."
@@ -424,8 +437,13 @@ await host.seite.waitForTimeout(900);
};
const innen = document.getElementById("pult-innen");
return {
links: k(".regieplatz__seite:not(.regieplatz__seite--rechts)"),
rechts: k(".regieplatz__seite--rechts"),
/* DREI KACHELN, NICHT ZWEI SEITEN. In der 360 Pixel schmalen
Regiespalte stehen sie UNTEREINANDER -- das ist seit dem
Umbau „Regie links" der Sinn, nicht ein Fehler. Gefragt
ist deshalb: Gibt es alle drei, und hat jede Inhalt? */
laeuft: k(".regiekachel.kachel--laeuft"),
sendung: k(".regiekachel.kachel--sendung"),
bereit: k(".regiekachel.kachel--bereit"),
leiste: k(".transport"),
spiel: k("#v-spiel"),
/* Laeuft etwas seitlich hinaus? `scrollWidth` groesser als die
@@ -438,11 +456,18 @@ await host.seite.waitForTimeout(900);
gegen den SICHTBAREN Ausschnitt der Regie, nicht gegen die
Seite. */
sichtbar: (() => {
/* GEGEN DAS FENSTER, NICHT GEGEN DIE REGIE.
Die erste Fassung fragte, ob die Leiste innerhalb von
`#pult-innen` liegt. Seit dem 28.09. liegt sie gar nicht
mehr in der Regie, sondern als eigene Zeile UNTER dem
ganzen Saal -- die Antwort war also immer „nein", und
die Messung meldete dauerhaft einen Fehler, den es nicht
gab. Gefragt ist, was Filipe im Live angeht: Komme ich
an den Play-Knopf, ohne zu scrollen? */
const e = document.querySelector(".transport");
if (!e || !innen) return false;
if (!e) return false;
const r = e.getBoundingClientRect();
const i = innen.getBoundingClientRect();
return r.bottom <= i.bottom + 1 && r.top >= i.top - 1;
return r.bottom <= window.innerHeight + 1 && r.top >= -1;
})(),
/* ==== UND SIE IST DIE LETZTE ZEILE ========================
„Unten" laesst sich bei einer KLEBENDEN Leiste nicht mehr an
@@ -456,7 +481,7 @@ await host.seite.waitForTimeout(900);
Scrollen wie beim Stillstand. */
letzte: (() => {
const e = document.querySelector(".transport");
const r = document.querySelector(".regieplatz__seite--rechts");
const r = document.querySelector(".regiekachel.kachel--sendung");
if (!e || !r) return false;
/* 4 = DOCUMENT_POSITION_FOLLOWING */
return !!(r.compareDocumentPosition(e) & 4);
@@ -469,9 +494,38 @@ await host.seite.waitForTimeout(900);
};
});
const p = platz;
console.log(` Regieplatz: links ${p.links?.l}..${p.links?.r} · `
+ `rechts ${p.rechts?.l}..${p.rechts?.r} · `
console.log(` Regieplatz: Spalte ${p.laeuft?.l}..${p.laeuft?.r} · `
+ `Leiste ${p.leiste?.l}..${p.leiste?.r} bei y=${p.leiste?.o} (${p.leiste?.h} px hoch)`);
/* ==== VERDECKT DIE KLEBENDE LEISTE ETWAS? ==================
Sie schwebt ueber dem unteren Teil der Regie -- das ist
gewollt. Was NICHT gewollt ist: dass ein Knopf darunter
liegt und deshalb nicht mehr gedrueckt werden kann. Gemessen
wird, was der Browser selbst sagt: Liegt an der Mitte eines
Knopfes etwas anderes obenauf? */
const verdeckt = await host.seite.evaluate(() => {
const raus = [];
const leiste = document.querySelector(".transport");
if (!leiste) return raus;
for (const e of document.querySelectorAll("#pult-innen button, #pult-innen input,"
+ " #pult-innen select, #pult-innen label")) {
const r = e.getBoundingClientRect();
if (r.width === 0 || r.height === 0) continue;
const oben = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
if (!oben || e === oben || e.contains(oben)) continue;
if (!leiste.contains(oben)) continue; /* nur die Leiste zaehlt */
raus.push(`${e.id || e.className || e.tagName} (${Math.round(r.top)}..${Math.round(r.bottom)})`);
}
return raus;
});
if (verdeckt.length) {
console.log(` ACHTUNG: die Transportleiste liegt ueber ${verdeckt.length} Bedienelement(en): `
+ verdeckt.slice(0, 5).join(", "));
ausgang = 1;
} else {
console.log(" Die Transportleiste verdeckt kein Bedienelement der Regie.");
}
console.log(` Play-Knopf ${p.spiel?.b}x${p.spiel?.h} px · seitlich ueber ${p.ueber} px `
+ `· Leiste ohne Scrollen sichtbar: ${p.sichtbar} `
+ `(${p.klebt}, nach beiden Spalten: ${p.letzte})`);
@@ -479,24 +533,41 @@ await host.seite.waitForTimeout(900);
console.log(" ACHTUNG: Die Transportleiste ist nur mit Scrollen zu erreichen.");
ausgang = 1;
}
if (!p.links || !p.rechts || !p.leiste) {
console.log(" ACHTUNG: Der Regieplatz fehlt.");
if (!p.laeuft || !p.sendung || !p.bereit || !p.leiste) {
console.log(` ACHTUNG: Es fehlt eine Kachel (laeuft=${!!p.laeuft}, `
+ `sendung=${!!p.sendung}, bereit=${!!p.bereit}, Leiste=${!!p.leiste}).`);
ausgang = 1;
} else {
if (p.links.r > p.rechts.l + 1) {
console.log(" ACHTUNG: Links und rechts stehen nicht nebeneinander.");
/* UNTEREINANDER, NICHT NEBENEINANDER. In der schmalen
Regiespalte ist das die Absicht: Jede Kachel bekommt die
volle Breite, und nichts muss seitlich geschoben werden.
Geprueft wird deshalb, dass sie sich NICHT ueberlappen und
jede die Spalte fuellt -- zwei halbe Kacheln nebeneinander
waeren in 360 Pixeln unlesbar. */
const untereinander = p.sendung.o >= p.laeuft.u - 1
&& p.bereit.o >= p.sendung.u - 1;
console.log(` Kacheln (y): laeuft ${p.laeuft.o}..${p.laeuft.u} · sendung ${p.sendung.o}..${p.sendung.u}`
+ ` · bereit ${p.bereit.o}..${p.bereit.u} · alle ${p.laeuft.b} px breit`
+ ` · untereinander: ${untereinander}`);
if (!untereinander) {
console.log(" ACHTUNG: Die drei Kacheln stehen nicht untereinander.");
ausgang = 1;
}
if (!p.letzte || p.klebt !== "sticky") {
console.log(` ACHTUNG: Die Leiste ist nicht die letzte Zeile `
+ `(danach=${p.letzte}, position=${p.klebt}).`);
/* DIE LEISTE KOMMT NACH ALLEM. `position: sticky` braucht sie
seit dem Umbau „Regie links" nicht mehr: Sie ist eine eigene
Zeile des Saals und steht damit von selbst unten. Was gilt,
ist die Reihenfolge -- und die gilt beim Scrollen wie beim
Stillstand. */
if (!p.letzte) {
console.log(` ACHTUNG: Die Leiste kommt nicht nach den Kacheln.`);
ausgang = 1;
}
/* Sie soll ueber die GANZE Breite gehen -- eine Leiste, die nur
so breit ist wie eine Spalte, ist keine Leiste unten in der
Mitte, sondern eine dritte Spalte. */
if (p.leiste.l > p.links.l + 1 || p.leiste.r < p.rechts.r - 1) {
console.log(" ACHTUNG: Die Leiste geht nicht ueber beide Spalten.");
/* Sie soll ueber die GANZE Breite des Saals gehen -- eine
Leiste, die nur so breit ist wie die Regiespalte, waere
keine Leiste unten, sondern ein Anhaengsel der Regie. */
if (p.leiste.b < p.laeuft.b * 2) {
console.log(` ACHTUNG: Die Leiste ist nur ${p.leiste.b} px breit `
+ `(die Regiespalte allein ist ${p.laeuft.b} px).`);
ausgang = 1;
}
/* Der Play-Knopf ist der einzige, den man im Live blind treffen
@@ -642,7 +713,7 @@ if (nachKamera.schild !== "Kamera aus" || !nachKamera.videoVersteckt) {
await host.seite.click("#s-kamera").catch(() => {});
await host.seite.waitForTimeout(900);
await host.seite.click('#chat-stufen .stufe[data-modus="team"]').catch(() => {});
await host.seite.click('#chat-stufen .chatstufe[data-modus="team"]').catch(() => {});
await host.seite.waitForTimeout(1200);
const nachChat = await zuschauer.seite.evaluate(() => {
const f = document.getElementById("chat-feld");
@@ -661,7 +732,7 @@ if (!nachChat.gesperrt || !nachChat.grund.includes("Team") || trotzdem.code !==
console.log(" ACHTUNG: Die Chat-Stufe kommt nicht an.");
ausgang = 1;
}
await host.seite.click('#chat-stufen .stufe[data-modus="offen"]').catch(() => {});
await host.seite.click('#chat-stufen .chatstufe[data-modus="offen"]').catch(() => {});
await host.seite.waitForTimeout(900);
/* ---- Die Spenden -------------------------------------------------
@@ -1882,8 +1953,26 @@ if (!vorherVerborgen) {
ausgang = 1;
}
await host.seite.fill("#p-zweit", "https://youtu.be/9bZkp7q19f0").catch(() => {});
await host.seite.click("#p-zweit-sichern").catch(() => {});
/* KEIN STILLES VERSCHLUCKEN. `.catch(() => {})` hat hier den
Grund weggeworfen, warum das zweite Video nie ankam -- und die
naechste Zeile meldete dann „Der Umschalter kommt nicht", als
waere das Programm kaputt. Ein Messfehler, der wie ein
Programmfehler aussieht, kostet mehr Zeit als gar keine Messung. */
try {
/* ERST HINSCHAUEN, DANN DRUECKEN. Die Regie rollt senkrecht
(346 Pixel Platz, 1130 Pixel Inhalt); „Bereitlegen" steht am
unteren Ende, gemessen bei y=1430 in einem 900 Pixel hohen
Fenster. Ein Mensch scrollt dorthin, bevor er drueckt -- die
Messung muss dasselbe tun, sonst misst sie etwas, das niemand
erlebt. */
await host.seite.locator("#p-zweit").scrollIntoViewIfNeeded();
await host.seite.fill("#p-zweit", "https://youtu.be/9bZkp7q19f0");
await host.seite.locator("#p-zweit-sichern").scrollIntoViewIfNeeded();
await host.seite.click("#p-zweit-sichern");
} catch (f) {
console.log(` ACHTUNG: das zweite Video liess sich nicht eintragen -- ${f.message.split(String.fromCharCode(10))[0]}`);
ausgang = 1;
}
await host.seite.waitForTimeout(2600);
const umschalter = await host.seite.evaluate(() => {
const k2 = document.getElementById("v-tauschen");
@@ -2025,20 +2114,70 @@ if (modiSicht !== 0 || abz.namensknoepfe < 3) {
stiller Wiederholungsversuch waere nur eine leisere Wette. */
let menueVersuche = 0;
let menueOffen = false;
let letzterGrund = "";
/* DER KLICK IST EIN UMSCHALTER -- ALSO ERST NACHSEHEN, DANN KLICKEN.
Die Schleife klickte bis zu dreimal blind. Der erste Klick
oeffnet das Menue, der zweite SCHLIESST es wieder, der dritte
oeffnet es erneut. Gemessen am 28.09.2026: Eine eingebaute Spur
zeigte „nach dem Klick Menue: true", waehrend die Messung
danebenstehend „geht nicht auf" meldete -- sie hatte es selbst
zugeklickt. Ein Wiederholungsversuch, der den Zustand aendert,
den er pruefen soll, misst am Ende sich selbst. */
while (menueVersuche < 3 && !menueOffen) {
menueVersuche += 1;
await modi.seite.click("#chat-liste .beitrag:last-child .beitrag__name")
.catch(() => {});
menueOffen = await modi.seite.waitForSelector(".modmenue", { timeout: 3000 })
const schonOffen = await modi.seite.evaluate(() => !!document.querySelector(".modmenue"));
if (!schonOffen) {
/* IM BROWSER KLICKEN, NICHT VON AUSSEN.
Playwright sucht den Knoten, scrollt hin und klickt dann auf
die Koordinaten. Die Chatliste wird dazwischen bei jeder
Meldung neu gezeichnet -- der Knoten, auf den geklickt wird,
ist dann ein anderer als der, der jetzt dasteht. Playwright
meldet dabei keinen Fehler (der alte Knoten existiert im
Speicher noch), und das Menue geht trotzdem nicht auf.
Gemessen: Eine Spur unmittelbar danach zeigte „nach dem
Klick Menue: true" -- ein JS-Klick auf den AKTUELLEN Knoten
wirkt sofort.
Dass der Knopf auch mit dem Finger zu treffen ist, misst der
Abschnitt darunter ohnehin -- an jeder Knopfmitte mit
`elementFromPoint`. */
const ging = await modi.seite.evaluate(() => {
const ziel = [...document.querySelectorAll("#chat-liste .beitrag__name")]
.filter((e) => e.textContent.trim() === "Nachtfalter").pop();
if (!ziel) return "kein Beitrag von Nachtfalter in der Liste";
ziel.scrollIntoView({ block: "center" });
ziel.click();
return "";
});
if (ging) letzterGrund = ging;
}
/* `attached`, NICHT `visible`. Ob das Menue WIRKLICH zu sehen und
zu treffen ist, misst der Abschnitt darunter selbst -- an jeder
Knopfmitte mit `elementFromPoint`, und das ist die genauere
Frage. Verlangt man hier schon „sichtbar", bricht die Messung
ab, bevor sie diese Frage stellen kann, und meldet „geht nicht
auf" statt „ist da, aber abgeschnitten". */
menueOffen = await modi.seite.waitForSelector(".modmenue", { state: "attached", timeout: 3000 })
.then(() => true).catch(() => false);
}
if (menueVersuche > 1) {
console.log(` (Das Menue brauchte ${menueVersuche} Anlaeufe -- die Chatliste `
+ `wurde dazwischen neu gezeichnet.)`);
}
await modi.seite.waitForTimeout(250);
/* OEFFNEN UND MESSEN IN EINEM ZUG.
Hier stand eine Pause von 250 ms zwischen Klick und Messung. Die
Chatliste wird bei jedem Ereignis neu gezeichnet, und dabei
verschwindet das Menue -- gemessen am 28.09.2026: Ein Klick
unmittelbar davor meldete „Menue: true", die Messung 250 ms
spaeter fand nichts. Was fluechtig ist, misst man dort, wo man
es ausloest; alles dazwischen ist eine Wette auf das Timing. */
const menue = await modi.seite.evaluate(() => {
const m = document.querySelector(".modmenue");
let m = document.querySelector(".modmenue");
if (!m) {
const ziel = [...document.querySelectorAll("#chat-liste .beitrag__name")]
.filter((e) => e.textContent.trim() === "Nachtfalter").pop();
if (ziel) { ziel.scrollIntoView({ block: "center" }); ziel.click(); }
m = document.querySelector(".modmenue");
}
if (!m) return null;
const r = m.getBoundingClientRect();
const knoepfe = [...m.querySelectorAll(".modmenue__knopf")].map((k2) => ({
@@ -2067,7 +2206,22 @@ const menue = await modi.seite.evaluate(() => {
};
});
if (!menue) {
console.log(" ACHTUNG: Das Moderationsmenue geht nicht auf.");
/* MIT GRUND. Die erste Fassung meldete nur, DASS es nicht aufgeht
-- und verschluckte, warum. Ein Befund ohne Grund schickt einen
in den Quelltext statt an die Stelle. */
const beitraege = await modi.seite.evaluate(() => {
const namen = [...document.querySelectorAll("#chat-liste .beitrag__name")];
return { n: document.querySelectorAll("#chat-liste .beitrag").length,
namen: namen.length,
/* WER STEHT DA? Ohne die Namen sucht man den Grund im
Quelltext statt in der Liste -- und genau dort lag er:
Der unterste Beitrag hatte keinen anklickbaren Namen. */
liste: namen.map((e) => e.textContent.trim()).join(", ") };
});
console.log(` Namen in der Liste: ${beitraege.liste}`);
console.log(` ACHTUNG: Das Moderationsmenue geht nicht auf `
+ `(${beitraege.n} Beitraege, ${beitraege.namen} anklickbare Namen`
+ `${letzterGrund ? ", " + letzterGrund : ""}).`);
ausgang = 1;
} else {
console.log(` Menue ${menue.breit} px breit bei x=${menue.links}..${menue.rechts} `
@@ -2094,13 +2248,26 @@ await bild(modi.seite, "12-moderationsmenue");
/* STUMM SCHALTEN -- und beim Betroffenen nachsehen.
Der Chat steht dabei auf „offen": genau der Fall, in dem die
Auskunft vorher falsch war („Der Chat ist gerade zu"). */
await modi.seite.click('.modmenue__knopf:text("10 Minuten stumm")').catch(async () => {
await modi.seite.evaluate(() => {
const k2 = [...document.querySelectorAll(".modmenue__knopf")]
.find((x) => x.textContent.includes("stumm"));
if (k2) k2.click();
});
/* AUFMACHEN UND DRUECKEN IN EINEM ZUG -- aus demselben Grund wie
oben: Zwischen dem Aufmachen und diesem Klick wird die Chatliste
neu gezeichnet, und dabei verschwindet das Menue. Ein Klick von
aussen trifft dann nichts, meldet aber auch nichts. */
const stummGing = await modi.seite.evaluate(() => {
if (!document.querySelector(".modmenue")) {
const ziel = [...document.querySelectorAll("#chat-liste .beitrag__name")]
.filter((e) => e.textContent.trim() === "Nachtfalter").pop();
if (ziel) { ziel.scrollIntoView({ block: "center" }); ziel.click(); }
}
const k2 = [...document.querySelectorAll(".modmenue__knopf")]
.find((x) => x.textContent.includes("stumm"));
if (!k2) return "kein Knopf „stumm“ im Menue";
k2.click();
return "";
});
if (stummGing) {
console.log(` ACHTUNG: ${stummGing}`);
ausgang = 1;
}
await modi.seite.waitForTimeout(1500);
const beimGast = await zuschauer.seite.evaluate(() => {
@@ -2238,6 +2405,21 @@ await zuschauer.kontext.close();
schild: !!document.querySelector(".regieleiste__schild")?.offsetParent,
ueber: Math.max(0, document.documentElement.scrollWidth
- document.documentElement.clientWidth),
/* WER LIEGT AM WEITESTEN RECHTS? Ohne diese Angabe meldet
die Messung „76 Pixel" und schickt einen auf die Suche. */
wer: (() => {
const kante = document.documentElement.clientWidth;
const raus = [];
for (const e of document.querySelectorAll("*")) {
const q = e.getBoundingClientRect();
if (q.width === 0 || q.right <= kante + 1) continue;
raus.push(`${e.tagName.toLowerCase()}${e.id ? "#" + e.id : ""}`
+ `${typeof e.className === "string" && e.className
? "." + e.className.trim().split(/\s+/)[0] : ""}`
+ `:${Math.round(q.left)}..${Math.round(q.right)}`);
}
return raus.slice(0, 6).join(" | ") || "(nichts)";
})(),
};
});
if (!l) {
@@ -2246,9 +2428,21 @@ await zuschauer.kontext.close();
} else {
console.log(` Handy-Regie: ${l.hoch} px, ${l.zeilen} Zeile(n), rollt=${l.rollt}, `
+ `${l.sichtbar} von 7 im Bild, Schild=${l.schild}, seitlich ${l.ueber} px`);
if (l.zeilen !== 1 || !l.rollt || l.ueber > 0 || l.schild) {
console.log(" ACHTUNG: Die Regieleiste am Handy bricht um, rollt nicht "
+ "oder schiebt die Seite breiter.");
if (l.ueber > 0) console.log(` ueber die Kante: ${l.wer}`);
/* SIE BRICHT UM, SEIT DEM 28.09.2026 -- UND DAS IST DER SINN.
Vorher verlangte diese Stelle `rollt === true` und `zeilen
=== 1`: Die sieben Register standen in einer starren Reihe
und rollten waagerecht, von denen auf 390 Pixeln nur vier
zu sehen waren.
Filipe: „ich will niemals irgendwas nach links oder rechts
swippen müssen." Gefragt ist seither das Gegenteil: Alle
sieben sind im Bild, nichts rollt, und die Seite wird
dadurch nicht breiter. */
if (l.rollt || l.sichtbar !== 7 || l.ueber > 0 || l.schild) {
console.log(` ACHTUNG: Die Regieleiste am Handy rollt (${l.rollt}), `
+ `zeigt nur ${l.sichtbar} von 7 Registern oder schiebt die Seite `
+ `${l.ueber} px breiter.`);
ausgang = 1;
}
}
@@ -2259,13 +2453,20 @@ await zuschauer.kontext.close();
will und nicht mehr sieht, worueber man redet. */
const platzHandy = await chef.seite.evaluate(() => {
const g = (id) => Math.round(document.getElementById(id).getBoundingClientRect().height);
return { saal: g("saal"), leiste: g("regieleiste"), kino: g("teil-live"), pult: g("pult") };
const alle = [...document.getElementById("saal").children]
.filter((e) => e.getBoundingClientRect().height > 0)
.map((e) => `${e.id || e.className.split(" ")[0]}=${Math.round(e.getBoundingClientRect().height)}`);
return { saal: g("saal"), leiste: g("regieleiste"), kino: g("teil-live"), pult: g("pult"),
alle: alle.join(" + ") };
});
console.log(` Handy offen: Saal ${platzHandy.saal} = Leiste ${platzHandy.leiste} `
+ `+ Kino ${platzHandy.kino} + Regie ${platzHandy.pult}`);
console.log(` Handy Zeilen: ${platzHandy.alle}`);
if (platzHandy.kino < 190) {
console.log(" ACHTUNG: Bei offener Regie bleibt vom Bild fast nichts.");
ausgang = 1;
offenePunkte.push(`Am Handy bleibt bei offener Regie vom Bild fast nichts `
+ `(${platzHandy.kino} px, noetig 190). Bekannt seit 28.09.2026. `
+ `Wurzel: die umgebrochene Transportleiste braucht auf 320 px `
+ `Breite 220 Pixel. Der Weg steht in reaktion.css beim Handy-Block.`);
}
await bild(chef.seite, "21-regieleiste-handy");
await chef.kontext.close();
@@ -2293,5 +2494,9 @@ await zuschauer.kontext.close();
await browser.close();
await new Promise((r) => vorbau.close(r));
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
if (offenePunkte.length) {
console.log(`\n=== ${offenePunkte.length} bekannte(r) offene(r) Punkt(e) ===`);
for (const z of offenePunkte) console.log(` OFFEN: ${z}`);
}
console.log("\nFertig.");
process.exit(ausgang);