/* ===================================================================== WIE DIE KOPFLEISTE AUF DEM HANDY STEHT -- UND WAS IM MENUE LANDET ===================================================================== Filipe, 09.09.2026: „es soll alles in einer reihe sein." Seit dem 25.09.2026 sorgt kopf.js dafuer, indem es MISST statt zu rechnen: Passt die Leiste nicht in eine Zeile, wandert der naechste Kandidat in ein Mehr-Menue. Diese Messung zeigt, was dabei herauskommt -- je Breite, mit Bild. SIE PRUEFT NICHTS. `pruef-handy` misst die Spanne der Oberkanten und wird rot, wenn zwei Zeilen entstehen. Hier geht es um das, was man nur sieht: Steht der Mehr-Knopf am richtigen Platz? Geht das Fach auf? Sind die Woerter darin lesbar? Aufrufen mit: node server/mess-kopfleiste.mjs ===================================================================== */ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { eigenerPort } from "./helfer-port.mjs"; const PORT = await eigenerPort(import.meta); const ordner = mkdtempSync(join(tmpdir(), "ws-kopf-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); process.env.PORT = String(PORT); process.env.SITE_ACCESS_SECRET = "lokaler-test"; process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); notbremse(240_000, "mess-kopfleiste"); await new Promise((r) => setTimeout(r, 900)); const BASIS = `http://127.0.0.1:${PORT}`; const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); const d = new DatabaseSync(process.env.WORKSPACE_DB); const jetzt = new Date().toISOString(); const sch = randomBytes(32).toString("hex"); d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") .run("code_kennung_schluessel", sch, jetzt); const anlegen = (name, rolle, code) => { const s = randomBytes(16).toString("hex"); const h = scryptSync(code, s, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); return d.prepare("INSERT INTO personen" + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + " VALUES (?,?,?,?,?,?,1,?) RETURNING id") .get(name, rolle, h, s, 32768, createHmac("sha256", sch).update(code).digest("hex"), jetzt).id; }; anlegen("Filipe", "admin", "CODE-DOGI-0001"); anlegen("Rieke", "hand", "CODE-HAND-0001"); d.close(); const an = await fetch(BASIS + "/workspace/api/anmelden", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }), }); const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); const pw = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); const browser = await pw.chromium.launch(); for (const [wie, breite] of [["320", 320], ["360", 360], ["390", 390], ["412", 412], ["768", 768], ["1280", 1280]]) { const kontext = await browser.newContext({ viewport: { width: breite, height: 860 }, deviceScaleFactor: 2, isMobile: breite < 700, hasTouch: breite < 700, }); await kontext.addCookies(keks.split("; ").map((k) => { const [n, ...r] = k.split("="); return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" }; })); const seite = await kontext.newPage(); await seite.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" }); await seite.waitForTimeout(1200); const lage = await seite.evaluate(() => { const leiste = document.querySelector(".kopfleiste__rechts"); const fach = document.getElementById("kopf-mehr-fach"); const knopf = document.getElementById("kopf-mehr"); const namen = (n) => [...n.children] .filter((k) => k.id !== "kopf-mehr-fach") .map((k) => `${String(k.className).split(" ")[0] || k.tagName.toLowerCase()}` + `(${Math.round(k.getBoundingClientRect().width)}x` + `${Math.round(k.getBoundingClientRect().height)})`); const oben = [...leiste.children] .filter((k) => k.id !== "kopf-mehr-fach" && !k.hidden && k.getBoundingClientRect().height > 0) .map((k) => Math.round(k.getBoundingClientRect().top)); return { inDerLeiste: namen(leiste), imFach: fach ? namen(fach) : [], mehrKnopf: knopf ? (knopf.hidden ? "versteckt" : "da") : "gibt es nicht", spanne: oben.length ? Math.max(...oben) - Math.min(...oben) : 0, hoehe: Math.round(document.querySelector(".kopfleiste").getBoundingClientRect().height), }; }); console.log(`\n=== ${wie} px ===`); console.log(` Leiste (${lage.spanne} px Spanne, Kopf ${lage.hoehe} px hoch):`); console.log(` ${lage.inDerLeiste.join(" ")}`); console.log(` Mehr-Knopf: ${lage.mehrKnopf}`); console.log(` im Fach: ${lage.imFach.join(" ") || "nichts"}`); /* Und einmal aufmachen -- ein Menue, das nicht aufgeht, ist schlimmer als kein Menue. */ if (lage.mehrKnopf === "da") { await seite.click("#kopf-mehr", { timeout: 4000 }) .catch((f) => console.log(" KLICK SCHEITERT: " + String(f.message).split(String.fromCharCode(10))[0].slice(0, 120))); await seite.waitForTimeout(400); const auf = await seite.evaluate(() => { const f = document.getElementById("kopf-mehr-fach"); if (!f || f.hidden) return null; const r = f.getBoundingClientRect(); return { kasten: `${Math.round(r.width)}x${Math.round(r.height)} bei ${Math.round(r.left)}`, ausDemBild: r.right > window.innerWidth + 1 || r.left < -1, woerter: [...f.children].map((k) => (k.textContent || "").trim().slice(0, 16)), zuKlein: [...f.children] .filter((k) => k.getBoundingClientRect().height < 44) .map((k) => (k.textContent || "").trim().slice(0, 12)), }; }); if (!auf) console.log(" FACH GEHT NICHT AUF"); else { console.log(` Fach offen: ${auf.kasten}` + (auf.ausDemBild ? " STEHT AUS DEM BILD" : "")); console.log(` ${auf.woerter.join(" | ")}`); if (auf.zuKlein.length) console.log(` ZU KLEIN: ${auf.zuKlein.join(", ")}`); await seite.screenshot({ path: `server/mess-kopf-${wie}.png` }).catch(() => {}); } } await kontext.close(); } await browser.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } process.exit(0);