diff --git a/server/pruef-arten.mjs b/server/pruef-arten.mjs index 4cbb8880..f98b3c35 100644 --- a/server/pruef-arten.mjs +++ b/server/pruef-arten.mjs @@ -156,6 +156,25 @@ await starten(); await stoppen(); const jetzt = new Date().toISOString(); + +/* ORTSZEIT FUER DAS TAGESDATUM, NICHT UTC (08.09.2026). + + Hier wurde der Tag mit `jetzt.slice(0, 10)` gebildet -- also aus UTC. + Zwischen Mitternacht und zwei Uhr Ortszeit ist das noch der VORTAG, + die Anwendung rechnet aber mit heuteLokal(). Ein Termin waere fuer + gestern angelegt und heute nicht gefunden worden; die Pruefung waere + nachts rot geworden, ohne dass am Code etwas falsch ist. + + Das ist mir an genau diesem Abend zweimal passiert: erst im + Messskript fuer die Startseite ("Heute=0", obwohl drei Termine + angelegt waren -- ich habe den Code verdaechtigt, der richtig lag), + und dann hier. pruef-struktur.mjs hat die zweite Stelle gefunden, + bevor sie schaden konnte. Genau dafuer gibt es sie. */ +const tagOrt = (versatzTage = 0) => { + const d = new Date(Date.now() + versatzTage * 86400000); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}` + + `-${String(d.getDate()).padStart(2, "0")}`; +}; let idDogi, idLuna, terminVorher, serienVorher; { const d = new DatabaseSync(DB); @@ -198,7 +217,7 @@ let idDogi, idLuna, terminVorher, serienVorher; idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001"); idLuna = anlegen("Luna", "creator", "CODE-CREA-0001"); - const heute = jetzt.slice(0, 10); + const heute = tagOrt(); const rein = d.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, creator_id, erstellt, erstellt_von) VALUES (?,?,?,?,?,?,?)`); rein.run("Alter Call", "call", heute + "T10:00", 30, idLuna, jetzt, idDogi); @@ -269,7 +288,7 @@ await starten(); misst, ist schlimmer als keine: Sie wird rot, jemand "repariert" etwas Heiles, und beim naechsten Mal glaubt ihr niemand mehr. */ const HORIZONT = 180; - const grenze = new Date(Date.now() + (HORIZONT + 1) * 86400000).toISOString().slice(0, 10); + const grenze = tagOrt(HORIZONT + 1); const ausSerie = d.prepare( "SELECT beginn FROM termine WHERE serie_id IS NOT NULL ORDER BY beginn").all(); const zuWeit = ausSerie.filter((z) => z.beginn.slice(0, 10) > grenze); @@ -301,7 +320,7 @@ await starten(); let erfunden = false; try { d.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, erstellt, erstellt_von) - VALUES ('Quatsch','quatsch',?,30,?,?)`).run(jetzt.slice(0, 10) + "T21:00", jetzt, idDogi); + VALUES ('Quatsch','quatsch',?,30,?,?)`).run(tagOrt() + "T21:00", jetzt, idDogi); } catch { erfunden = true; } ok(erfunden, "eine erfundene Art wird weiterhin abgelehnt"); d.close(); @@ -318,7 +337,7 @@ console.log("\n=== Anlegen mit den neuen Arten ==="); const keks = (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); ok(!!keks, "als DogFather angemeldet"); - const morgen = new Date(Date.now() + 86400000).toISOString().slice(0, 10); + const morgen = tagOrt(1); let angelegt = 0; for (const art of NEUE) { const r = await fetch(BASIS + "/workspace/api/termine", { diff --git a/server/pruef-ueberlappung.mjs b/server/pruef-ueberlappung.mjs new file mode 100644 index 00000000..5a89e133 --- /dev/null +++ b/server/pruef-ueberlappung.mjs @@ -0,0 +1,157 @@ +/* Liegt auf einer Seite irgendein Bedienelement UEBER einem anderen? + + Am 06.09.2026 lag der Sicht-Umschalter bei 412 px quer ueber dem + Chat-Knopf -- auf zwoelf Seiten. Wer auf "Meine Sicht" tippte, landete + im Chat. Gefunden wurde das durch Hinsehen; das hier misst es. */ +import { chromium } from "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"; +import { spawn } from "node:child_process"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { DatabaseSync } from "node:sqlite"; +import { scryptSync, randomBytes } from "node:crypto"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-kopf-")); +const DB = join(ordner, "w.db"); +const PORT = 4367, BASIS = `http://127.0.0.1:${PORT}`; +const start = () => spawn(process.execPath, ["server/index.js"], { + cwd: "C:/Users/qciga/Documents/Obelix/DogiHompage", + env: { ...process.env, WORKSPACE_DB: DB, PORT: String(PORT), + SITE_ACCESS_SECRET: "lokaler-test", SITE_PUBLIC_LAUNCH_AT: "2020-01-01T00:00:00+01:00" }, + stdio: ["ignore", "pipe", "pipe"] }); +const warten = async () => { + for (let i = 0; i < 80; i++) { await new Promise((r) => setTimeout(r, 250)); + try { const a = await fetch(BASIS + "/workspace/", { redirect: "manual" }); if (a.status < 500) return; } catch { /* noch nicht */ } } +}; +let kind = start(); await warten(); +kind.kill(); await new Promise((r) => setTimeout(r, 700)); +{ + const d = new DatabaseSync(DB); + const s = randomBytes(16).toString("hex"); + const h = scryptSync("CODE-DOGI-0001", s, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES ('Filipe','admin',?,?,?,1,?)") + .run(h, s, 32768, new Date().toISOString()); + d.close(); +} +kind = start(); await warten(); + +const a = await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }) }); +const kekse = (a.headers.getSetCookie?.() || []).map((z) => { + const [np] = z.split(";"); const [name, ...wert] = np.split("="); + return { name: name.trim(), value: wert.join("="), domain: "127.0.0.1", path: "/" }; +}); + +const SEITEN = ["start.html", "kalender.html", "aufgaben.html", "calls.html", "chat.html"]; +/* Die Breiten, an denen es wirklich klemmt: 320 = kleinstes uebliches + Geraet, 412 = haeufigste Android-Breite, 768 = Tablet hochkant, + 1280 = kleinster Laptop. Keine davon ist geraten. */ +const BREITEN = [320, 412, 768, 1280]; + +const b = await chromium.launch(); +let funde = 0, geprueft = 0; +for (const breite of BREITEN) { + const sitz = await b.newContext({ viewport: { width: breite, height: 900 } }); + await sitz.addCookies(kekse); + const p = await sitz.newPage(); + for (const seite of SEITEN) { + await p.goto(`${BASIS}/workspace/${seite}`, { waitUntil: "networkidle" }).catch(() => {}); + await p.waitForTimeout(900); + const treffer = await p.evaluate(() => { + const nimm = (n) => { const r = n.getBoundingClientRect(); return { r, n }; }; + /* Nur die Kopfleiste: dort stehen die Elemente am dichtesten, und + dort war der echte Fehler. */ + const leiste = document.querySelector(".kopfleiste"); + if (!leiste) return ["KEINE KOPFLEISTE"]; + const teile = [...leiste.querySelectorAll("button, a, select, .knopf")] + .filter((n) => { const r = n.getBoundingClientRect(); return r.width > 0 && r.height > 0; }) + .map(nimm); + const raus = []; + for (let i = 0; i < teile.length; i++) { + for (let j = i + 1; j < teile.length; j++) { + const A = teile[i], B = teile[j]; + if (A.n.contains(B.n) || B.n.contains(A.n)) continue; // verschachtelt ist kein Fehler + /* DASSELBE BEDIENELEMENT ZAEHLT NICHT (Befund beim Bauen). + + Der erste Lauf meldete zehnmal "Meine Sicht × Meine Sicht". + Das ist ein durchsichtiges