feat(backend): Aenderungsfeed per Long Polling (Schritt 4)

GET /documents/{id}/changes haelt die Anfrage offen und antwortet, sobald
sich etwas tut — kumuliertes Diff seit der bekannten Version, dazu die
Ereignisse mit ihrem Absender. Ist die Basis verdichtet oder hat der Client
noch gar nichts, kommt der Volltext statt der Operationen: ein Roundtrip und
ein Sonderzustand weniger als ein eigener Fehlerpfad.

Der Feed arbeitet auf der Historie, nicht am Dokument — ein geloeschtes
Dokument muss sein DELETED noch zustellen koennen.

Blockierend auf virtuellen Threads statt DeferredResult (D76-Nachtrag 5):
So behaelt der Endpunkt die aus der Spezifikation generierte Signatur, und
API-First bleibt fuer ihn unangetastet; ein Wartender kostet trotzdem fast
nichts. Geweckt wird nach dem Commit, nie davor, und ueber einen Stempel, den
der Aufrufer VOR dem Nachsehen liest — sonst ginge ein Signal aus der Luecke
dazwischen verloren.

122 Tests. Das Szenario "ein Wartender wird geweckt" misst die Dauer: Ohne
das bestuende es auch dann, wenn der Wartende bloss in den Timeout liefe und
danach die Aenderung vorfaende. Gegenprobe: Benachrichtigung entfernt ->
genau dieses Szenario faellt; Volltext-Rueckfall entfernt -> genau jenes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-26 17:16:13 +02:00
co-authored by Claude Opus 5
parent ae85503b79
commit 741b41ef11
21 changed files with 818 additions and 25 deletions
+3 -3
View File
@@ -7,10 +7,10 @@ Status-Sync), später Tenzu-Adapter.
**Stand:** Gerüst steht — Dokumenten-CRUD mit Historie und Wiederherstellung,
API-First aus `src/main/resources/openapi/api.yaml`, H2 mit Liquibase.
Kommandos in README.md hier. Live-Editing (D76,
`docs/live-editing-proposal.md`) ist in Arbeit: Schritte 13 der Reihenfolge
`docs/live-editing-proposal.md`) ist in Arbeit: Schritte 14 der Reihenfolge
dort sind gebaut (Zeilen-Diff in `de.werkbaum.diff`, Historie in zwei Ebenen,
`PATCH /content` im `LiveEditingService`), ab Schritt 4 (`GET /changes` per
Long Polling) steht es aus.
`PATCH /content` und der Änderungsfeed im `LiveEditingService`); offen sind
`PATCH /title`, das Master-Passwort und der Client.
## Konventionen
- Kotlin, **Spring Boot 4**, Gradle (Kotlin DSL), JDK 21.