feat(ui): Meldungen mit Rückgängig, dazu jsdom und ein Fehler in der letzten Behebung

Vorher bestätigte nichts irgendetwas, und Fehler verschwanden still: Die
Handler fingen sie mit `catch { /* bleibt stehen */ }` ab. Wer eine Datei
löschen wollte und nicht durfte, sah genau dasselbe wie jemand, bei dem es
klappte – nichts.

Neu: components/Toasts.tsx. Kein Context, sondern Modulzustand, damit auch
api/client.ts melden kann, ohne Komponente zu sein. Löschen, Umbenennen und
Verschieben bekommen ein „Undo" – beim Löschen ist das der Papierkorb plus
einen Aufruf, kostet also fast nichts. Ordner löschen bekommt bewusst keines:
Der Ordner ist wirklich weg, nur seine Dateien liegen im Papierkorb, und ein
Knopf, der etwas anderes tut als er verspricht, wäre schlimmer als keiner.

Dazu die erste Animation im Projekt – und gleich die Regel dazu:
prefers-reduced-motion wird respektiert.

**jsdom ist jetzt in den devDependencies**, und der erste DOM-Test hat sofort
einen Fehler in der Behebung von vorgestern gefunden:

Der Zähler für selbst ausgelöste Verlaufsschritte setzt voraus, dass jedes
`back()` auch ein `popstate` nach sich zieht. Das gilt nicht – folgt im selben
Durchlauf ein `pushState`, verwirft der Browser den Sprung. Der Zähler blieb
dann stehen, und die nächste echte Zurück-Geste wurde verschluckt: Statt die
Ansicht zu schließen, hätte man die Anwendung verlassen.

Jetzt wird erst im nächsten Durchlauf entschieden und geprüft, ob der eigene
Eintrag noch obenauf liegt. Nur dann kommt es zum Sprung – und dann folgt das
`popstate` garantiert. Der Zuhörer hängt am Modul statt an der Ansicht; vorher
entfernte ihn deren Aufräumer, bevor der eigene Sprung ausgeführt war.

Ehrlich dokumentiert: Den ursprünglichen Fehler bilden diese Tests **nicht**
ab. jsdom verwirft den Sprung, das verhängnisvolle popstate entsteht dort nie
– mit der alten, kaputten Fassung blieben alle Tests grün. Dafür bräuchte es
einen echten Browser. Steht so in TESTING.md und im Test selbst.

Geprüft: tsc sauber, 41 Vitest-Tests (4 neue unter Mutation nachgewiesen),
alle 234 Prüfungen der sieben Selbsttest-Suiten grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-18 14:05:12 +02:00
1 parent 7d45941c03
commit 94a803bf7a
9 files changed
+1131 -51

No files matched your search

+57
View File
@@ -1468,6 +1468,63 @@ das war die Asymmetrie, über die man stolperte.
---
## Meldungen mit Rückgängig (18.09.2026)
Vorher bestätigte **nichts** irgendetwas, und Fehler verschwanden still: Die
Handler fingen sie mit `catch { /* bleibt stehen */ }` ab. Wer eine Datei
löschen wollte und nicht durfte, sah genau dasselbe wie jemand, bei dem es
klappte – nichts.
- [ ] Datei löschen → Meldung mit „Undo", Datei ist zurück 🔎
- [ ] Mehrere löschen → eine Meldung, „Undo" holt alle zurück
- [ ] Umbenennen → „Undo" stellt den alten Namen her
- [ ] Verschieben → „Undo" bringt die Datei an den alten Ort
- [ ] Ordner löschen → **kein** „Undo", stattdessen der Hinweis auf den
Papierkorb (der Ordner selbst ist wirklich weg)
- [ ] Ohne Schreibrecht löschen → rote Meldung mit dem Satz des Servers
- [ ] Netz trennen, dann löschen → „No connection to the server."
- [ ] Meldung verschwindet nach 4 s, mit Knopf erst nach 10
- [ ] Fünfte Meldung verdrängt die erste (höchstens vier stehen)
- [ ] Betriebssystem auf „Bewegung reduzieren" → keine Einblendung
## Der Zurück-Fehler und was der Test **nicht** kann (16.–18.09.2026)
Auf „Edit" in der PDF-Ansicht schloss sich die Datei, statt den Editor zu
öffnen. Beim Wechsel räumt React erst die alte Ansicht ab; deren Aufräumer rief
`history.back()`, das verzögert eintraf und den Eintrag verbrauchte, den der
Editor gerade angelegt hatte.
**Die erste Behebung war selbst fehlerhaft** und ist erst durch die neuen
jsdom-Tests aufgefallen: Sie zählte selbst ausgelöste Sprünge mit. Das setzt
voraus, dass jedes `back()` auch ein `popstate` nach sich zieht – und das gilt
nicht. Folgt im selben Durchlauf ein `pushState`, verwirft der Browser den
Sprung. Der Zähler blieb dann für immer stehen, und die **nächste** echte
Zurück-Geste wurde verschluckt: Statt die Ansicht zu schließen, hätte man die
Anwendung verlassen.
Jetzt wird erst im nächsten Durchlauf entschieden **und** geprüft, ob der
eigene Eintrag überhaupt noch obenauf liegt. Nur dann kommt es zum Sprung – und
dann folgt das `popstate` garantiert.
- [x] Vier jsdom-Tests in `useBackClose.test.tsx`, unter Mutation nachgewiesen:
Der Schalter bleibt stehen → der Übergangstest wird rot.
> **Was diese Tests nicht leisten.** Den ursprünglichen Fehler bilden sie
> **nicht** ab. jsdom verwirft einen noch nicht ausgeführten Sprung, sobald
> `pushState` folgt – das verhängnisvolle `popstate` entsteht dort also nie.
> Nachgeprüft, indem die alte, kaputte Fassung wieder eingesetzt wurde: **alle
> Tests blieben grün.** Chrome auf dem Telefon verhält sich anders, und dort
> war der Fehler zu sehen.
>
> Wer diesen Fall absichern will, braucht einen echten Browser (Playwright).
> Bis dahin ist der Beleg ein Mensch mit einem Telefon.
Seit dem 18.09.2026 ist **jsdom** in den devDependencies. Die Vitest-Umgebung
bleibt `node` (schnell); wer ein DOM braucht, schreibt
`// @vitest-environment jsdom` an den Anfang seiner Datei.
---
## Absender / Korrespondent (23.08.2026)
Das zweite Ordnungsmerkmal neben den Etiketten. Unterschied: genau **einer**