diff --git a/server/pruef-handy-teamdogi.mjs b/server/pruef-handy-teamdogi.mjs index cdfbeff8..c5a77c12 100644 --- a/server/pruef-handy-teamdogi.mjs +++ b/server/pruef-handy-teamdogi.mjs @@ -351,6 +351,32 @@ const messen = () => { const hatText = (e.textContent || "").trim().length > 0; const bedienbarHier = e.matches(BEDIENBAR) || !!e.closest(BEDIENBAR); if (!hatText && !bedienbarHier) return false; + + /* WAS IN EINEM ROLLENDEN KASTEN STEHT, LAEUFT NICHT UEBER + (geschaerft 22.09.2026). + + Der Rundgang meldete drei Befunde, bei jeder Rolle dieselben: + `button.schnitt bis 481px` auf aufgaben.html und die + Rechtetafel `table bis 877px`. Nachgemessen bei 390 px: + Das Dokument ist exakt 390 px breit, NICHTS laeuft ueber -- + beide stehen in einem Kasten mit `overflow-x: auto`, und der + rollt absichtlich waagerecht. Eine breite Tabelle auf einem + schmalen Schirm ist genau so geloest, wie man es loest. + + Diese drei Meldungen kamen bei JEDEM Lauf. Und eine Warnung, + die immer kommt, ist keine Warnung mehr -- man klickt sie weg + und uebersieht ab da auch die echte. (Hausregel vom 03.09.2026, + damals zu einer Loeschwarnung, die bei jedem Aufruf ansprang.) + + DIE REGEL IST SCHMAL: Es zaehlt nur, wenn ein Vorfahre + ausdruecklich `overflow-x: auto` oder `scroll` traegt. Ein + Element, das einfach so hinausragt, wird weiter gemeldet -- + und `overflow: hidden` gilt NICHT, denn dort ist der Inhalt + wirklich weg und nicht erreichbar. */ + for (let v = e.parentElement; v && v !== document.body; v = v.parentElement) { + const ox = getComputedStyle(v).overflowX; + if (ox === "auto" || ox === "scroll") return false; + } return r.right > breite + 2; }).slice(0, 4).map((e) => `${name(e)} bis ${Math.round(e.getBoundingClientRect().right)}px`); @@ -427,6 +453,21 @@ const messen = () => { /* ---- Zu kleine Tippziele ----------------------------------------- */ const klein = bedienbar.filter((e) => { + /* WAS KEINE KLICKS ANNIMMT, IST KEIN TIPPZIEL + (geschaerft 22.09.2026). + + Gemeldet wurde `a.k-pille 8x8` auf kalender.html -- die Punkte, + die im Monatsraster am Handy fuer je einen Termin stehen. Sie + sind ABSICHTLICH 8 px klein und tragen `pointer-events: none`: + Der Tipp gehoert der ganzen Tageszelle, nicht dem Punkt. Ein + 8-px-Ziel waere ein Nadeloehr; die Zelle daneben ist 46 px und + oeffnet den Tag. + + Wer keine Klicks annimmt, kann auch kein zu kleines Ziel sein. + Die Regel ist damit schaerfer, nicht laxer: Ein Knopf, der + Klicks annimmt und zu klein ist, wird weiter gemeldet -- die + Gegenprobe weiter unten weist genau das nach. */ + if (getComputedStyle(e).pointerEvents === "none") return false; const r = e.getBoundingClientRect(); /* 43,5 STATT 44: Ein Knopf von 43,98 px wird als „44x44" angezeigt und waere trotzdem ein Fund -- ein halbes Pixel @@ -553,6 +594,17 @@ for (const [breite, hoehe, geraet] of BREITEN) { if (m.type() === "error") konsole.push(m.text().slice(0, 100)); }); seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message.slice(0, 100))); + /* WELCHE Datei fehlt -- die Konsole sagt es nicht (22.09.2026). + Der Rundgang meldete tagelang "404 (Not Found)" ohne Adresse. + Eine Pruefung, die einen Fehler findet, ihn aber nicht + auffindbar macht, kostet mehr Zeit als sie spart: Man weiss, + dass etwas kaputt ist, und muss es trotzdem von Hand suchen. */ + seite.on("requestfailed", (r) => { + konsole.push(`Anfrage gescheitert: ${r.url().replace(BASIS, "")}`); + }); + seite.on("response", (r) => { + if (r.status() === 404) konsole.push(`404: ${r.url().replace(BASIS, "")}`); + }); /* ---- Anmelden, wie ein Mensch es tut ---------------------------- */ let drin = false; @@ -702,13 +754,47 @@ melde("\n --- Gegenprobe (beide Fehler MÜSSEN gefunden werden) ---"); winzig.textContent = "x"; winzig.style.cssText = "width:18px;height:18px"; document.body.append(winzig); + + /* ---- UND DIE ZWEI AUSNAHMEN VOM 22.09.2026 ------------------- + Beide Regeln machen die Messung ENGER. Eine engere Messung kann + auch zu eng sein -- dann fiele echter Schaden durch, und der + Lauf bliebe gruen. Deshalb hier vier Elemente statt zwei: zwei, + die gemeldet werden MUESSEN, und zwei, die es NICHT duerfen. */ + + /* (a) In einem rollenden Kasten -- darf NICHT gemeldet werden. + So steht die Rechtetafel da, und so gehoert eine breite + Tabelle auf einen schmalen Schirm. */ + const roller = document.createElement("div"); + roller.id = "gp-roller"; + roller.style.cssText = "overflow-x:auto;width:200px"; + const drin = document.createElement("div"); + drin.textContent = "Steht in einem Kasten, der absichtlich rollt"; + drin.style.cssText = "width:900px;height:20px;white-space:nowrap"; + roller.append(drin); + document.body.append(roller); + + /* (b) Winzig, aber nimmt keine Klicks an -- darf NICHT gemeldet + werden. So sind die Terminpunkte im Kalender gebaut. */ + const stumm = document.createElement("button"); + stumm.id = "gp-stumm"; + stumm.textContent = "o"; + stumm.style.cssText = "width:8px;height:8px;pointer-events:none"; + document.body.append(stumm); }); const g = await seite.evaluate(messen); const ok1 = g.ueberstand.length > 0; const ok2 = g.klein.length > 0; + /* Die zwei Ausnahmen duerfen NICHT in der Liste stehen. Gemessen + wird das an ihrer Groesse: 900 px waere der Roller-Inhalt, 8x8 + der stumme Knopf. */ + const ok3 = !g.ueberstand.some((x) => /9\d\dpx/.test(x)); + const ok4 = !g.klein.some((x) => /\b8×8\b/.test(x)); melde(` ${ok1 ? "ok " : "FEHL"} ein zu breites Element wird gemeldet (${g.ueberstand.join(", ") || "nichts"})`); melde(` ${ok2 ? "ok " : "FEHL"} ein zu kleiner Knopf wird gemeldet (${g.klein.join(", ") || "nichts"})`); + melde(` ${ok3 ? "ok " : "FEHL"} was in einem rollenden Kasten steht, NICHT`); + melde(` ${ok4 ? "ok " : "FEHL"} was keine Klicks annimmt, auch NICHT`); if (!ok1 || !ok2) { fehler += 1; probleme.push("GEGENPROBE VERSAGT — die Messung findet nichts mehr"); } + if (!ok3 || !ok4) { fehler += 1; probleme.push("GEGENPROBE VERSAGT — die Messung meldet Absicht als Fehler"); } await kontext.close(); } diff --git a/server/pruef-nachfrage.mjs b/server/pruef-nachfrage.mjs index 3eaa9d1e..fba04ae1 100644 --- a/server/pruef-nachfrage.mjs +++ b/server/pruef-nachfrage.mjs @@ -97,7 +97,27 @@ const nativ = []; for (const f of skripte) { if (f === DER_NOTNAGEL) continue; const code = ohneKommentare(lies(join(JS_ORDNER, f))); - for (const m of code.matchAll(/(?:^|[^.\w])(confirm|prompt)\s*\(/g)) { + /* DAS MUSTER HATTE EINE LUECKE, UND ZWAR DIE GROESSTE MOEGLICHE + (gefunden am 22.09.2026). + + Vorher stand hier `(?:^|[^.\w])(confirm|prompt)\s*\(`. Das + `[^.\w]` sollte fremde Methoden ausschliessen -- etwas wie + `angebot.prompt()` (so heisst die Installations-Aufforderung des + Browsers) ist kein nativer Dialog und darf nicht als Fund zaehlen. + + Nur trifft dieser Ausschluss ausgerechnet die HAEUFIGSTE + Schreibweise: `window.prompt(` hat einen Punkt davor. Die Pruefung + war damit gruen, waehrend in teamlage.js ZWEI native prompt() + standen -- einer davon seit Wochen. + + Genau das Muster, vor dem dieses Haus warnt: eine Pruefung, die + laeuft, gruen ist und das Falsche prueft. Gefunden hat es nicht + sie, sondern eine Suche von Hand. + + Jetzt zwei Faelle statt einem: mit `window.` davor -- oder ganz + ohne Punkt. Alles andere (`irgendwas.prompt()`) bleibt aussen + vor, und das ist weiterhin richtig. */ + for (const m of code.matchAll(/(?:^|[^.\w])(?:window\s*\.\s*)?(confirm|prompt)\s*\(/g)) { const zeile = code.slice(0, m.index).split("\n").length; nativ.push(`${f}:${zeile} → ${m[1]}()`); } @@ -280,6 +300,15 @@ pruef("die Speicherwege melden der Wache, dass nichts mehr zu retten ist", ------------------------------------------------------------------ */ console.log("\n --- Gegenproben (jede MUSS anschlagen) ---"); +/* DIE GEGENPROBE PRUEFT JETZT BEIDE SCHREIBWEISEN. Haette sie das + vorher getan, waere die Luecke im Muster am selben Tag aufgefallen, + an dem es geschrieben wurde. */ +pruef("Gegenprobe: ein erfundenes window.prompt() würde gefunden", + /(?:^|[^.\w])(?:window\s*\.\s*)?(confirm|prompt)\s*\(/.test( + "const g = window.prompt('Warum nicht?');")); +pruef("Gegenprobe: eine fremde Methode NICHT (angebot.prompt())", + !/(?:^|[^.\w])(?:window\s*\.\s*)?(confirm|prompt)\s*\(/.test( + "angebot.prompt();")); pruef("Gegenprobe: ein erfundenes confirm() würde gefunden", /(?:^|[^.\w])(confirm|prompt)\s*\(/.test( ohneKommentare("function x(){ if (!confirm('weg?')) return; }"))); diff --git a/server/pruef-werdegang.mjs b/server/pruef-werdegang.mjs index 3c3a3acb..36120d63 100644 --- a/server/pruef-werdegang.mjs +++ b/server/pruef-werdegang.mjs @@ -600,8 +600,58 @@ melde("=== 11. Im Browser ==="); { const { chromium } = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); - const browser = await chromium.launch(); - const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } }); + /* AUF DER ECHTEN ADRESSE MESSEN (berichtigt 22.09.2026). + ================================================================== + Bis heute oeffnete dieser Abschnitt `http://127.0.0.1/...` -- also + die ADRESSE DER AGENTUR. Auf der ist `person.haus` nicht "crew", + und `bereicheFuer()` liefert eine andere Kachelliste. Die Kachel + zu dieser Seite steht dort nicht drin, also fand `kopf.js` keine + Farbe: `data-ton=null`. + + Drei Pruefungen waren deshalb rot, und zwar dauerhaft -- an einer + Seite, die in Wirklichkeit in Ordnung ist. Nachgemessen: Alle vier + Rollen (admin, hand, linke, modi) HABEN die Kachel, sobald das + Haus stimmt. + + Genau dieselbe Falle wie am 21.09.2026 bei pruef-rueckmeldung. + Dort ging es ueber `node:http` mit Host-Kopf; ein Browser kann das + nicht (Chromium verbietet den Host-Kopf). Der Weg im Haus ist ein + selbst unterschriebener Vorbau plus `--host-resolver-rules` -- + derselbe wie in pruef-willkommen und pruef-zuteilung. */ + const { createServer: httpsServer } = await import("node:https"); + const { request: httpWeiter } = await import("node:http"); + const { execFileSync } = await import("node:child_process"); + const { mkdtempSync: tempOrdner, readFileSync: liesDatei } = await import("node:fs"); + const { tmpdir: tempWurzel } = await import("node:os"); + const { join: pfad } = await import("node:path"); + + const zOrdner = tempOrdner(pfad(tempWurzel(), "wg-zeugnis-")); + const zKey = pfad(zOrdner, "k.pem"), zCrt = pfad(zOrdner, "z.pem"); + execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", + "-keyout", zKey, "-out", zCrt, "-days", "2", "-subj", `/CN=${CREW}`, + "-addext", `subjectAltName=DNS:${CREW}`], { stdio: "ignore" }); + + const HTTPS_PORT = PORT + 1; + const vorbau = httpsServer( + { key: liesDatei(zKey), cert: liesDatei(zCrt) }, + (von, an) => { + const weiter = httpWeiter({ + host: "127.0.0.1", port: PORT, path: von.url, method: von.method, + headers: { ...von.headers, host: CREW }, + }, (antwort) => { an.writeHead(antwort.statusCode, antwort.headers); antwort.pipe(an); }); + weiter.on("error", () => { try { an.destroy(); } catch { /* Zugabe */ } }); + von.on("error", () => {}); + von.pipe(weiter); + }); + await new Promise((r) => vorbau.listen(HTTPS_PORT, "127.0.0.1", r)); + const SEITE = `https://${CREW}:${HTTPS_PORT}`; + + const browser = await chromium.launch({ + args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"], + ignoreHTTPSErrors: true, + }); + const kontext = await browser.newContext({ + viewport: { width: 1280, height: 1000 }, ignoreHTTPSErrors: true }); const seite = await kontext.newPage(); const konsole = []; seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); }); @@ -609,10 +659,10 @@ melde("=== 11. Im Browser ==="); await kontext.addCookies(kDogi.split("; ").filter(Boolean).map((teil) => { const i = teil.indexOf("="); - return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" }; + return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: CREW, path: "/" }; })); - await seite.goto(`http://127.0.0.1:${PORT}/workspace/werdegang.html`, { waitUntil: "networkidle" }); + await seite.goto(`${SEITE}/workspace/werdegang.html`, { waitUntil: "networkidle" }); await seite.waitForTimeout(900); const liste = await seite.evaluate(() => ({ @@ -741,6 +791,7 @@ melde("=== 11. Im Browser ==="); ok(konsole.length === 0, konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 120) : "keine Fehler im Browser"); await browser.close(); + await new Promise((r) => vorbau.close(r)); } /* ======================================================================= diff --git a/server/workspace.js b/server/workspace.js index 11b707df..a3822f75 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -5723,6 +5723,20 @@ 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), + /* 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 + behandelte den 404 schon richtig -- aber der Browser + protokolliert ihn trotzdem, und im Handy-Rundgang stand + deshalb bei jedem Modi "404 (Not Found)" in der Konsole. + Genau derselbe Fall wie am 19.09.2026 bei /anruf/adressen. + + EIGENES FELD, kein Stellvertreter: `darf_verteilen` sieht + aehnlich aus, ist aber nicht dasselbe -- ein Manager darf + verteilen und fuehrt Team Dogi nicht. Zwei Regeln, zwei + Felder; eines fuer beides waere die Stelle, an der es beim + naechsten Umbau auseinanderlaeuft. */ + fuehrt_team: fuehrtTeamDogi(person), /* DIE KACHELN, WENN SIE NICHT IM BROWSER STEHEN DUERFEN. Fuer die fuenf bekannten Rollen steht hier `null`, und die diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 065cc8f1..707cb9d7 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`. --> - - + + - +