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:
+167
-1
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user