fix(editor): Knopf sicherte nichts, solange nichts geändert war

Gemeldet: „I took a manual snapshot ... List is empty." Nachgestellt und
bestätigt — schlimmste Bauart: nichts gespeichert, Knopf bestätigte
trotzdem.

Ursache ist der Denkfehler aus D54-Nachtrag 1: `snapshotNow()` vergleicht
bei leerer Liste gegen `snapBase`, den Text beim Öffnen des Dokuments. Für
den Takt richtig (bloßes Ansehen sammelt nichts), für den Knopf falsch. Die
Begründung „bei unverändertem Text steht er schon oben in der Liste" gilt
eben nicht, wenn die Liste leer ist — und das ist genau der Anlass des
Knopfes: öffnen, sichern, DANN ändern. Die frühere Messung begann mit einer
Eingabe und prüfte damit die Wirkung, nicht den Anlass.

- `snapshotNow(manuell)`: Die snapBase-Sperre gilt nur noch für den Takt;
  der Knopf vergleicht allein gegen den letzten Eintrag.
- `loadSnapshot()` sagt jetzt ebenfalls `true` — bewusstes Wegleg-Ereignis.
- `setInterval(() => snapshotNow(), …)`: Ein durchgereichtes Argument wäre
  wahr und hebelte die Sperre aus (dieselbe Falle wie D17-Nachtrag 4).

Nachgemessen — Knopf: 10 Zeilen, Klick ohne jede Eingabe -> [10]; nach einer
Änderung -> [10,11]; ältesten laden -> zurück auf 10, kein Doppel-Eintrag.
Takt (2 s für die Prüfung): nur angesehen, 3 Takte -> 0; nach Änderung -> 1;
weitere Takte -> 1.

D54-Nachtrag 2. 243 Tests grün (die Stände selbst sind nicht test-gedeckt,
sie leben in app.js — Lücke benannt).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-24 11:28:49 +02:00
co-authored by Claude Opus 5
parent f7d963809a
commit c4f70ebb24
2 changed files with 64 additions and 5 deletions
+18 -5
View File
@@ -2964,12 +2964,22 @@ function dropOldestSnap(){
außen vor: Ihr Text ist schreibgeschützt, ein alter Stand ließe sich dort
gar nicht wieder einsetzen — Stände zu sammeln, die niemand laden kann,
wäre nur Ballast. */
function snapshotNow(){
/* `manuell` schaltet die `snapBase`-Sperre ab — und das ist der ganze
Unterschied zwischen Takt und Knopf. `snapBase` ist der Text beim Öffnen des
Dokuments; solange nichts daran geändert wurde, soll der **Takt** nichts
sammeln (sonst legte jedes bloße Ansehen einen Stand an). Für den Knopf wäre
dieselbe Sperre falsch: „vor der großen Änderung sichern" heißt gerade, dass
noch nichts geändert ist. Bei leerer Liste ist der Text dann **nirgends**
gesichert, und der Knopf bestätigte etwas, das nicht stimmte (gemeldet und
nachgestellt, D54-Nachtrag 2). Verglichen wird für ihn nur noch gegen den
**letzten Eintrag** — der Doppelte bleibt vermieden, und die Zusage „dein
Stand ist gesichert" wird in jedem Fall wahr. */
function snapshotNow(manuell){
const d = activeDoc();
if(!d || src.readOnly) return false;
const text = src.value;
const list = snaps[d.id] || (snaps[d.id] = []);
const letzter = list.length ? list[list.length-1].text : snapBase;
const letzter = list.length ? list[list.length-1].text : (manuell ? null : snapBase);
if(text === letzter) return false;
list.push({t: Date.now(), text});
while(list.length > SNAP_KEEP) list.shift();
@@ -2978,7 +2988,10 @@ function snapshotNow(){
if(!snapMenu.hidden) renderSnapMenu();
return true;
}
setInterval(snapshotNow, SNAP_EVERY);
/* `() => snapshotNow()`, nicht `snapshotNow` direkt: Ein durchgereichtes
Argument wäre wahr und hebelte die `snapBase`-Sperre des Takts aus —
dieselbe Falle wie bei `setAppHeight` (D17-Nachtrag 4). */
setInterval(() => snapshotNow(), SNAP_EVERY);
const snapBtn = document.getElementById('snapBtn');
const snapAddBtn = document.getElementById('snapAddBtn');
@@ -3023,7 +3036,7 @@ function renderSnapMenu(){
nicht drin ist — sonst wäre er das Einzige, was der Griff verlöre. */
function loadSnapshot(s){
if(src.readOnly) return;
snapshotNow();
snapshotNow(true); /* bewusstes Wegleg-Ereignis wie der Knopf, nicht der Takt */
closeSnapMenu();
if(!replaceTextUndoable(s.text)) render();
snapBase = src.value;
@@ -3054,7 +3067,7 @@ snapBtn.addEventListener('click', e => {
sieht man den neuen Eintrag entstehen. */
snapAddBtn.addEventListener('click', e => {
e.stopPropagation();
snapshotNow();
snapshotNow(true);
flashBtn(snapAddBtn);
});
document.addEventListener('click', e => {