Files
dogfather-universe/server/pruef-stelle.mjs
T
DogFatherGitandClaude Opus 5 7305f694d5 Die Stelle bleibt -- auch wenn man ueber die Kopfzeile zurueckgeht
Filipe, zum vierten Mal: "muss ich jedes mal wenn ich von einer seite
zurück scrolle immer wieder runter scrollen um weiter zu schauen und
das nervt."

DIE WIEDERHERSTELLUNG GAB ES SEIT DEM 20.09. -- sorgfaeltig gebaut,
mit Warten auf die nachgeladene Hoehe, mit Aufgeben nach drei
Sekunden, mit "wer selbst scrollt, gewinnt".

SIE LIEF NUR FAST NIE. Denn sie lief ausschliesslich bei
`navigation.type === "back_forward"`, also beim Zurueck-Knopf des
Browsers. Im Haus geht man anders zurueck: ueber die Kopfzeile ("TEAM
DOGI › HIGHLIGHTS"), und dort steht ein ganz gewoehnliches
`<a href="start.html">`. Fuer den Browser ist das eine Fahrt
VORWAERTS. Die Bedingung war also falsch -- und der else-Zweig
loeschte die eben gemerkte Stelle noch dazu.

IM KOPF DERSELBEN DATEI STAND DIE LOESUNG ALS ABSICHT: "Ein eigener
Merker faengt den Fall ab, dass der Zurueck-Knopf der Kopfleiste
benutzt wurde." Gebaut wurde er nie.

UND DESHALB HAT ES IN JEDEM TEST FUNKTIONIERT: Wer mit `goBack()`
prueft, prueft den einen Weg, den niemand geht. Gemessen am 21.09.:
ueber goBack sauber wiederhergestellt, ueber den Link in der
Kopfzeile oben gelandet. Gefunden hat es Filipes Bildschirmvideo --
17 Sekunden, drei Bilder, und der Ablauf war eindeutig.

Der Merker gibt es jetzt wirklich: Beim Klick auf einen EIGENEN
Zurueck-Weg (`.zurueck`, `.zurueck-knopf`) wird das Ziel notiert; wer
dort binnen 30 Sekunden ankommt, bekommt seine Stelle zurueck. Er
gilt fuer genau eine Ankunft -- wer die Seite eine Minute spaeter
normal oeffnet, faengt wieder oben an. Und NUR fuer diese zwei
Klassen, nicht fuer jeden Link: Sonst landete man beim Oeffnen eines
Brettes mitten darin.

Dazu der Rueckwaerts-Speicher des Browsers: Holt er eine Seite aus dem
Speicher, laeuft keine Zeile mehr, und unser `manual` verbietet ihm
gleichzeitig, die Stelle selbst wiederherzustellen. Ein `pageshow`
faengt das ab. Dieser Fall ist NICHT gemessen -- in Playwright greift
der Speicher nicht -- sondern aus der Spezifikation begruendet. Das
steht auch so im Kommentar, damit niemand spaeter glaubt, er sei
nachgewiesen.

NEU: pruef-stelle.mjs, 9 Pruefungen. Sie geht beide Wege zurueck --
Kopfzeile UND Browser -- und haelt ausdruecklich fest, dass der erste
KEIN "back_forward" ist. Dazu die Gegenprobe, dass eine frisch
geoeffnete Seite oben anfaengt: Ohne sie waere alles auch dann gruen,
wenn die Seite IMMER zur alten Stelle springt, und das waere
schlimmer als das Problem.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-21 14:24:19 +02:00

248 lines
10 KiB
JavaScript

/* =====================================================================
pruef-stelle.mjs — Bleibt man, wo man war? (21.09.2026)
Filipe, zum vierten Mal: "muss ich jedes mal wenn ich von einer seite
zurück scrolle immer wieder runter scrollen um weiter zu schauen und
das nervt, wieso bleibt es nicht sofort da wo ich dran war anstatt
dass ich immer wieder ganz runter scrollen muss."
---------------------------------------------------------------------
WARUM ES DREIMAL ALS BEHOBEN GALT UND NIE BEHOBEN WAR
Die Wiederherstellung in kopf.js gibt es seit dem 20.09. Sie ist
sorgfaeltig gebaut -- sie merkt sich die Stelle je Adresse, wartet,
bis die nachgeladene Seite hoch genug ist, und hoert auf, sobald der
Mensch selbst scrollt.
Sie lief nur fast nie. Denn sie lief NUR bei
`navigation.type === "back_forward"`, also beim Zurueck-Knopf des
Browsers. Im Haus geht man aber anders zurueck: ueber die Kopfzeile,
und dort steht ein ganz normales `<a href="start.html">`. Fuer den
Browser ist das eine Fahrt VORWAERTS. Die Bedingung war falsch, und
der `else`-Zweig loeschte die eben gemerkte Stelle sogar noch.
IM KOPF DERSELBEN DATEI STAND DIE LOESUNG ALS ABSICHT: "Ein eigener
Merker faengt den Fall ab, dass der Zurueck-Knopf der Kopfleiste
benutzt wurde." Gebaut wurde er nie. Ein Kommentar, der eine Absicht
beschreibt, erfuellt sie nicht.
UND DESHALB HAT ES IN JEDEM TEST FUNKTIONIERT: Wer mit
`page.goBack()` prueft, prueft den einen Weg, den niemand geht.
Gemessen am 21.09.: ueber `goBack()` sauber wiederhergestellt,
ueber den Link in der Kopfzeile oben gelandet. Man muss den Weg
gehen, den der Mensch geht.
---------------------------------------------------------------------
WAS HIER GEMESSEN WIRD
1. Ueber den Link in der Kopfzeile zurueck -> Stelle bleibt
2. Ueber den Zurueck-Knopf des Browsers -> Stelle bleibt
3. GEGENPROBE: Eine Seite frisch oeffnen -> faengt oben an
Die dritte ist nicht Zierde. Ohne sie waere die Pruefung auch dann
gruen, wenn die Seite IMMER an die alte Stelle springt -- und das
waere schlimmer als das Problem: Wer eine Kachel antippt, will oben
anfangen.
Aufrufen mit: node server/pruef-stelle.mjs
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { request as httpAnfrage } from "node:http";
import { eigenerPort } from "./helfer-port.mjs";
const PORT = await eigenerPort(import.meta, "pruef-stelle");
const ordner = mkdtempSync(join(tmpdir(), "ws-stelle-"));
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(180_000, "pruef-stelle");
await new Promise((r) => setTimeout(r, 900));
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const CREW = "crew.dogfather-universe.com";
function roh(pfad, koerper) {
return new Promise((fertig, schief) => {
const a = httpAnfrage({
host: "127.0.0.1", port: PORT, path: pfad, method: koerper ? "POST" : "GET",
headers: { Host: CREW, ...(koerper ? { "Content-Type": "application/json" } : {}) },
}, (antwort) => {
let text = "";
antwort.on("data", (s) => { text += s; });
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
});
a.on("error", schief);
if (koerper) a.write(JSON.stringify(koerper));
a.end();
});
}
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
{
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const salz = randomBytes(16).toString("hex");
const hash = scryptSync("CODE-DOGI-0001", salz, 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 (?,?,?,?,?,1,?)")
.run("Filipe", "admin", hash, salz, 32768, new Date().toISOString());
d.close();
}
let an = await roh("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001" });
if (an.code === 400 && an.text.includes("alter_offen")) {
an = await roh("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001", alter_ok: true });
}
const keks = [].concat(an.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
ok(!!keks, "angemeldet");
/* ------------------------------------------------------------------- */
let pw = null;
try {
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
} catch { /* dritter Ausgang */ }
if (!pw) {
melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen.");
melde(" Das ist kein Befund, aber auch kein bestandener Test.");
melde(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
process.exit(fehler ? 1 : 0);
}
const browser = await pw.chromium.launch();
/** Scrollt auf der Startseite nach unten, oeffnet ein Brett und kommt
* auf dem uebergebenen Weg zurueck. Gibt zurueck, wo man landet. */
async function hinUndZurueck(wieZurueck) {
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
const [name, ...rest] = z.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await ctx.newPage();
try {
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1700);
const hoch = await seite.evaluate(() => document.documentElement.scrollHeight);
/* DIE HOEHE GEHOERT IN DIE BEDINGUNG. Auf einer Seite, die kuerzer
ist als das Fenster, kann man gar nicht scrollen -- dann misst
alles Folgende nichts. */
if (hoch < 1600) return { zuKurz: hoch };
await seite.evaluate(() => window.scrollTo(0, 1200));
await seite.waitForTimeout(400);
const vor = await seite.evaluate(() => Math.round(window.scrollY));
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=highlight`,
{ waitUntil: "networkidle" });
await seite.waitForTimeout(1400);
if (wieZurueck === "link") {
/* Ueber die Kopfzeile -- der Weg, den Filipe im Video geht.
`evaluate` statt `click`, weil der Link je nach Breite hinter
dem Knopf liegen kann; das Ereignis ist dasselbe. */
const gabEs = await seite.evaluate(() => {
const a = document.querySelector("a.zurueck");
if (!a) return false;
a.click();
return true;
});
if (!gabEs) return { keinLink: true };
await seite.waitForNavigation({ waitUntil: "networkidle" }).catch(() => {});
} else {
await seite.goBack({ waitUntil: "load" });
}
await seite.waitForTimeout(3000);
const nach = await seite.evaluate(() => ({
y: Math.round(window.scrollY),
art: performance.getEntriesByType("navigation")[0]?.type,
wo: location.pathname,
}));
return { vor, ...nach };
} finally {
await ctx.close();
}
}
try {
melde("\n=== 1. Zurueck ueber die Kopfzeile ===");
{
const r = await hinUndZurueck("link");
ok(!r.zuKurz && !r.keinLink,
r.zuKurz ? `die Startseite ist nur ${r.zuKurz}px hoch -- nichts zu messen`
: r.keinLink ? "es gibt keinen Zurueck-Link in der Kopfzeile"
: "der Weg ueber die Kopfzeile ist da");
if (!r.zuKurz && !r.keinLink) {
ok(r.wo === "/workspace/start.html", `man landet auf der Startseite (${r.wo})`);
/* DASS ES EBEN KEIN "back_forward" IST, ist der Kern der Sache --
und wird deshalb ausdruecklich festgehalten. Waere es eines,
pruefte dieser Abschnitt denselben Weg wie der naechste. */
ok(r.art !== "back_forward",
`und zwar als gewoehnliche Fahrt vorwaerts (${r.art}) -- genau der Fall, `
+ "der bisher nicht wiederhergestellt wurde");
ok(r.y >= r.vor - 80,
`die Stelle bleibt: ${r.vor}px hin, ${r.y}px zurueck`);
}
}
melde("\n=== 2. Zurueck ueber den Browser ===");
{
const r = await hinUndZurueck("back");
ok(!r.zuKurz, r.zuKurz ? `zu kurz (${r.zuKurz}px)` : "die Seite ist hoch genug");
if (!r.zuKurz) {
ok(r.art === "back_forward", `der Browser meldet ein Zurueck (${r.art})`);
ok(r.y >= r.vor - 80, `die Stelle bleibt: ${r.vor}px hin, ${r.y}px zurueck`);
}
}
melde("\n=== 3. Gegenprobe: frisch geoeffnet faengt oben an ===");
{
/* Ohne diese Zeile waere alles oben auch dann gruen, wenn die
Seite IMMER an die alte Stelle springt. Das waere schlimmer als
das Problem: Wer eine Kachel antippt, will oben anfangen. */
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
const [name, ...rest] = z.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await ctx.newPage();
try {
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1500);
await seite.evaluate(() => window.scrollTo(0, 1200));
await seite.waitForTimeout(400);
/* Neu aufrufen, ohne ueber einen Zurueck-Weg zu kommen. */
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(2600);
const y = await seite.evaluate(() => Math.round(window.scrollY));
ok(y < 80, `frisch geoeffnet steht die Seite oben (${y}px)`);
} finally {
await ctx.close();
}
}
} finally {
await browser.close();
}
melde(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
process.exit(fehler ? 1 : 0);