Eine Eingabe geht nicht mehr wortlos verloren

Filipe: "Was passiert, wenn jemand eine Funktion abbricht?"

GEMESSEN: Acht Dialoge im Workspace enthalten Eingabefelder --
"Aufgabe bearbeiten" (10 Felder), der Tageseintrag in der Leistung
(8), Ziele, Netzwerk, Import, ein neuer Chat-Raum, ein Creator. Bei
allen galt: Esc, Klick daneben oder "Abbrechen" wirft ALLES weg,
wortlos. Wer zehn Felder ausgefuellt hat und mit dem Daumen den Rand
trifft, faengt von vorn an.

Neu: window.verwurfWache() in nachfrage.js.

BEIDE SCHLIESSWEGE, denn sie laufen verschieden:
  Esc / Klick daneben  loest `cancel` aus, close() wird NICHT gerufen
  "Abbrechen"-Knopf    ruft close(), loest kein `cancel` aus
Wer nur einen abfaengt, hat eine halbe Sicherung -- und die ist
schlimmer als keine, weil man ihr vertraut.

NICHT VON HAND VERTEILT: Jeder Dialog mit Feldern bekommt sie
automatisch (ausser dem Nachfrage-Dialog selbst -- er wuerde beim
Schliessen nach sich selbst fragen, in sich selbst, und haenge fuer
immer). Wer morgen einen neunten baut, hat sie, ohne daran zu denken.

UND SIE FRAGT NUR BEI WIRKLICHER AENDERUNG. Beim Oeffnen wird ein
Abbild der Felder genommen, beim Schliessen verglichen. Wer einen
Dialog aufmacht und gleich wieder zu, merkt nichts.

ZWEI SACHEN BEIM BAUEN GEMESSEN STATT VERMUTET:

  MEINE EIGENE "ROBUSTHEIT" HAT DIE WACHE STILL AUSGESCHALTET.
  Gegen den Fall "Felder werden erst nach dem Oeffnen gefuellt" hatte
  ich einen zweiten Schnappschuss beim Hineinklicken (`focusin`)
  eingebaut. Gemessen: Der entstand NACH dem Tippen -- Fokus und
  Werteingabe passieren im selben Atemzug. `beimOeffnen` war danach
  gleich dem getippten Text, und der Dialog ging wortlos zu. Die
  Sicherung sah eingebaut aus und tat nichts.
  Jetzt wird im naechsten BILD nachgetragen: Was das Skript beim
  Oeffnen nachtraegt, ist drin; getippt haben kann in derselben
  Sechzehntelsekunde niemand.
  (Nachgemessen: Alle acht Dialoge fuellen heute VOR showModal.)

  ZWEIMAL ESC HINTEREINANDER SCHLIESST TROTZDEM. Das ist Chromiums
  "close watcher": Eine Seite darf den Nutzer nicht mit Esc
  einsperren. Die Regel ist richtig; dagegen anzubauen waere falsch.
  Sie steht deshalb im Code und in der Pruefung -- festgehalten,
  nicht umgangen. Der Knopf unterliegt ihr nicht, und am Handy gibt
  es ohnehin kein Esc.

Elf Speicherwege rufen `vergessen()`, damit nach erfolgreichem
Speichern nicht gefragt wird. Eine Warnung nach dem Speichern waere
genau die, die man wegklickt -- und danach auch die echte.

pruef-nachfrage 49/0 (war 33), davon zehn am Bildschirm:
  unberuehrt geht er ohne Nachfrage zu
  nach einer Aenderung fragt Esc nach, der Dialog bleibt offen
  "Weiter bearbeiten" laesst ihn offen UND der Text steht noch da
  auch der Abbrechen-Knopf fragt -- jedes Mal
  "Verwerfen" schliesst wirklich, und der Text steht nirgends

pruef-aufgabenbrett, -leistung, -uebersicht-browser, -chat-optik,
-css-klassen gruen, pruef-tippziele 11/0.
This commit is contained in:
2026-09-20 15:11:25 +02:00
parent 285038f400
commit cc39552b4f
42 changed files with 874 additions and 515 deletions
+167 -1
View File
@@ -131,7 +131,16 @@ for (const s of seiten) {
const html = lies(join(SEITEN_ORDNER, s));
const geladen = [...html.matchAll(/<script src="assets\/js\/([a-z0-9-]+\.js)/g)]
.map((m) => m[1]);
const kannFragen = geladen.some((g) => nutzer.has(g));
/* ZWEI GRUENDE, die Datei zu brauchen (20.09.2026):
fragen -- oder einen Dialog mit Eingabefeldern haben. Seit die
Verwurfwache existiert, bewacht `nachfrage.js` jeden solchen
Dialog von selbst, auch ohne einen einzigen `frageNach`-Aufruf
in der Seite. Ohne diesen zweiten Grund wuerde eine Seite mit
einem reinen Formular-Dialog hier als „totes Gewicht" gemeldet --
und jemand naehme die Datei heraus, die dort gerade arbeitet. */
const hatDialogMitFeld = [...html.matchAll(/<dialog[^>]*>[\s\S]*?<\/dialog>/g)]
.some((m) => /<(input|textarea|select)/.test(m[0]));
const kannFragen = geladen.some((g) => nutzer.has(g)) || hatDialogMitFeld;
const hatDatei = html.includes("assets/js/nachfrage.js");
if (kannFragen) { mitNutzung++; fragendeSeiten.push(s); if (!hatDatei) ohneDatei.push(s); }
else if (hatDatei) totesGewicht.push(s);
@@ -235,6 +244,37 @@ pruef("jeder Aufruf sagt, WAS passiert", gezaehlt > 0 && ohneWas.length === 0,
pruef("eine Person löschen verlangt den Namen abgetippt",
/tippen:\s*v\.person\.name/.test(lies(join(JS_ORDNER, "personen.js"))));
/* ---------------------------------------------------------------------
DIE VERWURFWACHE — beide Schliesswege, und kein Dauerfeuer
------------------------------------------------------------------ */
const wache = notnagel;
pruef("die Verwurfwache faengt Esc ab (das `cancel`-Ereignis)",
/addEventListener\('cancel'/.test(wache),
"ohne sie liefe Esc durch -- der Browser ruft close() dabei NICHT auf");
pruef("und sie faengt auch close() ab (die Abbrechen-Knoepfe)",
/dlg\.close = function/.test(wache),
"ohne sie liefe der Knopf durch -- er loest kein `cancel` aus");
pruef("sie ersetzt close() nur AM DIALOG, nicht am Prototyp",
!/HTMLDialogElement\.prototype\.close\s*=/.test(wache),
"ein globaler Eingriff traefe den Nachfrage-Dialog selbst -- der ginge nie mehr zu");
pruef("sie fragt nur bei wirklicher Aenderung",
/const geaendert = \(\) =>/.test(wache) && /if \(!geaendert\(\)\)/.test(wache),
"eine Warnung, die immer kommt, ist keine Warnung mehr");
pruef("sie bewacht sich nicht selbst",
/dialog:not\(\.nachfrage\)/.test(wache),
"sonst fragte der Nachfrage-Dialog beim Schliessen nach sich selbst");
/* Und die Speicherwege sagen ihr Bescheid -- sonst fragte sie nach
jedem erfolgreichen Speichern. */
let speicherwege = 0;
for (const f of skripte) {
if (f === DER_NOTNAGEL) continue;
speicherwege += [...ohneKommentare(lies(join(JS_ORDNER, f)))
.matchAll(/\.vergessen\?\.\(\)/g)].length;
}
pruef("die Speicherwege melden der Wache, dass nichts mehr zu retten ist",
speicherwege >= 8, `${speicherwege} Stellen rufen vergessen()`);
/* ---------------------------------------------------------------------
DIE GEGENPROBEN — jede muss anschlagen
------------------------------------------------------------------ */
@@ -426,6 +466,132 @@ if (process.env.OHNE_BROWSER === "ja") {
seite.url().includes("start.html"), seite.url());
}
/* ===================================================================
DIE VERWURFWACHE AM ECHTEN BILDSCHIRM (20.09.2026)
Alles oben ist Textsuche. Sie beweist, dass der Code dasteht --
nicht, dass Esc wirklich abgefangen wird, dass ein unberührter
Dialog still zugeht und dass „Verwerfen" wirklich verwirft.
Geprüft wird am größten Formular des Hauses: „Aufgabe bearbeiten",
zehn Felder. Wer dort tippt und mit dem Daumen den Rand trifft,
verlor bis heute alles, wortlos.
=================================================================== */
console.log("\n --- Die Verwurfwache am Bildschirm ---");
/* Eine Aufgabe, damit es etwas zu bearbeiten gibt. */
{
const dbz = new DatabaseSync(process.env.WORKSPACE_DB);
const wer = dbz.prepare("SELECT id FROM personen WHERE rolle = 'admin' LIMIT 1").get()?.id;
dbz.prepare("INSERT INTO aufgaben (titel, beschreibung, status, verantwortlich_id,"
+ " erstellt_von, erstellt) VALUES (?,?,?,?,?,?)")
.run("Zum Bearbeiten", "Ein Text, der nicht verlorengehen darf.",
"offen", wer, wer, new Date().toISOString());
dbz.close();
}
await seite.goto(BASIS + "/workspace/aufgaben.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
const auf = async () => {
await seite.click(".karte, .aufgabe, [data-id]", { timeout: 5000 }).catch(() => {});
await seite.waitForTimeout(500);
return seite.evaluate(() =>
!!document.getElementById("bearbeiten-dialog")?.open);
};
/* Erst über den Bearbeiten-Knopf an der Karte. */
let offen = await seite.evaluate(() => {
const k = [...document.querySelectorAll("button")]
.find((b) => /bearbeiten/i.test(b.textContent || "") || /bearbeiten/i.test(b.title || ""));
if (k) { k.click(); return true; }
return false;
});
await seite.waitForTimeout(700);
let istAuf = await seite.evaluate(() =>
!!document.getElementById("bearbeiten-dialog")?.open);
if (!istAuf) istAuf = await auf();
pruef("der Bearbeiten-Dialog geht auf", istAuf);
if (istAuf) {
/* 1. UNBERÜHRT -> still zu. Das ist der wichtigere Fall: Wer
nichts getippt hat, darf nichts gefragt werden. */
await seite.keyboard.press("Escape");
await seite.waitForTimeout(500);
const nachEsc = await seite.evaluate(() => ({
dialogZu: !document.getElementById("bearbeiten-dialog")?.open,
gefragt: !!document.querySelector("dialog.nachfrage[open]"),
}));
pruef("unberührt geht er ohne Nachfrage zu", nachEsc.dialogZu && !nachEsc.gefragt,
`zu: ${nachEsc.dialogZu}, gefragt: ${nachEsc.gefragt}`);
/* 2. GETIPPT -> Esc fragt nach. */
await seite.evaluate(() => {
const k = [...document.querySelectorAll("button")]
.find((b) => /bearbeiten/i.test(b.textContent || "") || /bearbeiten/i.test(b.title || ""));
k?.click();
});
await seite.waitForTimeout(600);
await seite.fill("#b-titel", "Etwas ganz anderes");
await seite.keyboard.press("Escape");
await seite.waitForTimeout(600);
const nachTippen = await seite.evaluate(() => ({
nochOffen: !!document.getElementById("bearbeiten-dialog")?.open,
gefragt: !!document.querySelector("dialog.nachfrage[open]"),
text: document.querySelector('dialog.nachfrage [data-teil="was"]')?.textContent || "",
}));
pruef("nach einer Änderung fragt Esc nach", nachTippen.gefragt,
`gefragt: ${nachTippen.gefragt}`);
pruef("und der Dialog bleibt solange offen", nachTippen.nochOffen);
pruef("die Frage sagt, was verloren geht",
/verloren/i.test(nachTippen.text), nachTippen.text);
/* 3. „Weiter bearbeiten" -> der Text steht noch da. */
await seite.click('dialog.nachfrage [data-teil="nein"]');
await seite.waitForTimeout(500);
const nachWeiter = await seite.evaluate(() => ({
nochOffen: !!document.getElementById("bearbeiten-dialog")?.open,
titel: document.getElementById("b-titel")?.value || "",
}));
pruef("Weiter bearbeiten laesst den Dialog offen", nachWeiter.nochOffen);
pruef("und der getippte Text steht noch da",
nachWeiter.titel === "Etwas ganz anderes", `„${nachWeiter.titel}"`);
/* 4. DER KNOPF-WEG -- und er muss JEDES MAL fragen.
----------------------------------------------------------------
NICHT noch einmal Esc. Gemessen am 20.09.2026: Ein ZWEITES Esc
hintereinander schliesst den Dialog, egal was die Seite tut.
Das ist Chromiums „close watcher": Eine Seite darf den Nutzer
nicht mit Esc einsperren. Die Regel ist richtig, und dagegen
anzubauen waere falsch -- also wird sie hier festgehalten statt
umgangen.
Der Knopf unterliegt ihr nicht. Er ist auch der Weg, den man am
Handy nimmt, wo es kein Esc gibt. */
await seite.click("#b-abbrechen");
await seite.waitForTimeout(600);
const nachKnopf = await seite.evaluate(() => ({
nochOffen: !!document.getElementById("bearbeiten-dialog")?.open,
gefragt: !!document.querySelector("dialog.nachfrage[open]"),
}));
pruef("auch der Abbrechen-Knopf fragt nach",
nachKnopf.gefragt && nachKnopf.nochOffen,
`gefragt: ${nachKnopf.gefragt}, noch offen: ${nachKnopf.nochOffen}`);
await seite.click('dialog.nachfrage [data-teil="ja"]', { timeout: 6000 });
await seite.waitForTimeout(700);
const nachVerwerfen = await seite.evaluate(() =>
!document.getElementById("bearbeiten-dialog")?.open);
pruef('Verwerfen schliesst den Dialog wirklich', nachVerwerfen);
/* Und danach ist nichts uebernommen worden -- verwerfen heisst
verwerfen, nicht speichern. */
const inListe = await seite.evaluate(() =>
document.body.textContent.includes("Etwas ganz anderes"));
pruef("und der verworfene Text steht nirgends in der Liste", !inListe);
}
pruef("keine Abstürze im Browser", abstuerze.length === 0, abstuerze[0] || "");
await browser.close();