docs(plan): block leases with auto-lease on insert and release grace period
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 11:10:15 +01:00
1 parent af578d8237
commit 74797e3f44
1 file changed
+11 -5
+11 -5
View File
@@ -22,7 +22,7 @@ Ausgangslage: Der Editor sendet alle 800 ms das ganze Dokument (`PUT /documents/
|---|---|
| **Operations-Log** | Jede Änderung ist eine kleine Operation (Text eines Blocks ändern, Block einfügen, verschieben, löschen). Der Server prüft, wendet an und vergibt eine fortlaufende Nummer pro Dokument (`seq`). Wer `seq` kennt, holt sich einfach alles danach. |
| **Version pro Block** | Jede Operation nennt die Blockversion, auf der sie basiert. Passt sie nicht mehr, liegt ein Konflikt vor, und der Server erkennt ihn sicher. |
| **Lease (Sperre) pro Block** | Wer in einen Bullet klickt, bekommt ihn für 20 Sekunden reserviert und verlängert das, solange er tippt. Andere sehen „Anna bearbeitet …“ und können diesen Bullet so lange nicht ändern, alle anderen aber schon. Das verhindert fast alle Konflikte. |
| **Lease (Sperre) pro Block** | Wer in einen Bullet klickt, bekommt ihn für 20 Sekunden reserviert und verlängert das, solange er tippt. Neu erzeugte Bullets gehören automatisch ihrem Ersteller. Andere sehen „Anna bearbeitet …“ und können diesen Bullet so lange nicht ändern, alle anderen aber schon, auch im selben Ast. Das verhindert fast alle Konflikte. |
| **Konfliktkopie** | Kommt es trotzdem zum Konflikt (z. B. Lease abgelaufen, weil jemand offline war), behält der Block die zuerst gespeicherte Fassung. Der andere Text wird als eigener Block direkt darunter eingefügt und als Konflikt markiert. Nichts geht verloren. |
| **Lokale Warteschlange** | Jede Änderung landet sofort in IndexedDB und wird von dort gesendet. Jede Operation hat eine eindeutige ID; doppeltes Senden ist harmlos (idempotent). |
| **Transport-Stufen** | Daten fließen immer über `POST /sync`. Benachrichtigung über WebSocket → SSE → Long-Polling → Intervall-Abfrage, je nachdem, was durchkommt. |
@@ -161,7 +161,9 @@ Alle Operationen eines Dokuments laufen nacheinander: ein Mutex pro Dokument im
6. **`move`:** Wird abgelehnt, wenn ein Zyklus entstünde (Block unter seinen eigenen Nachfahren) oder ein anderer Client eine Lease auf dem Block hält. Der Client lädt dann den Stand neu.
7. **`delete`:** Wird abgelehnt, wenn ein anderer Client gerade einen Block in diesem Ast bearbeitet (Lease). Sonst Grabstein für den ganzen Ast.
8. **`insert`:** Ist der Elternblock gelöscht, wird unter dem nächsten lebenden Vorfahren eingefügt.
9. **Leases:** Vergabe nur, wenn frei, abgelaufen oder schon im Besitz desselben Clients; Gültigkeit 20 s ab letzter Bestätigung. Der Client sendet alle 7 s einen `/sync` mit seinen gehaltenen Blöcken (Herzschlag).
9. **Leases:** Vergabe nur, wenn frei, abgelaufen oder schon im Besitz desselben Clients; Gültigkeit 20 s ab letzter Bestätigung. Ein Client darf beliebig viele Blöcke gleichzeitig halten. Der Client sendet alle 7 s einen `/sync` mit allen gehaltenen Blöcken (ein Herzschlag für alle).
10. **Automatische Lease beim Einfügen:** Ein `insert` vergibt dem einfügenden Client die Lease auf den neuen Block, ohne eigene Anfrage. Der Block ist vorher niemandem bekannt, es kann also keinen Wettlauf geben.
11. **Keine Lease auf Elternblöcke:** Einfügen, Einrücken und Ausrücken unter einem Elternblock brauchen keine Lease auf diesen. Gleichzeitiges Einfügen zweier Personen in denselben Ast ergibt zwei Blöcke (Sortierung über `order_key`). Gesperrt wird nur der Inhalt einzelner Blöcke sowie Löschen/Verschieben von Ästen, in denen jemand anders einen Block hält (Regeln 6 und 7).
### 4.3 Ablauf beim Tippen
@@ -194,9 +196,12 @@ Neues Modul `frontend/src/lib/docSync/` (ersetzt das Autosave in `DocumentEditor
- **Tippen:** Textänderungen eines Blocks werden 300 ms gesammelt. Pro Block ist höchstens eine Textoperation unterwegs; weitere Änderungen werden in die nächste zusammengefasst. So bleibt `baseVersion` immer eindeutig.
- **Senden:** immer nur ein `/sync` gleichzeitig. Das hält die Reihenfolge der eigenen Operationen stabil. Wiederholung mit Backoff (1 s, 2 s, 4 s … max. 30 s).
- **Leases:**
- Beim Fokussieren eines Bullets wird die Lease angefordert. Bis zur Antwort darf getippt werden.
- Wird sie verweigert: Der Bullet wird schreibgeschützt („Ben bearbeitet gerade“), schon Getipptes wird als neuer Block darunter eingefügt.
- Nach 60 s ohne Eingabe oder beim Verlassen des Bullets gibt der Client die Lease frei.
- **Tippen wartet nie auf eine Lease.** Beim Fokussieren eines bestehenden Bullets wird die Lease mit dem nächsten `/sync` angefordert; getippt werden darf sofort.
- **Neue Bullets (Enter, Einfügen) sind sofort eigene**, ohne Anfrage (Regel 10). Bullet für Bullet zu schreiben erzeugt keinen einzigen Sperrwechsel.
- **Nachlauf:** Verlassene Bullets bleiben noch 20 s gesperrt und werden erst dann freigegeben, wenn man nicht zurückgekehrt ist. Springen mit Pfeiltasten oder Ergänzen im Bullet davor braucht keine neue Anfrage.
- **Verweigert** (nur bei bestehenden Bullets möglich, die jemand anders schon hält): Der Bullet wird schreibgeschützt („Ben bearbeitet gerade“), schon Getipptes wird als neuer, eigener Block darunter eingefügt.
- Nach 60 s ohne Eingabe gibt der Client alle Leases frei, auch beim fokussierten Bullet.
- **Bewusst nicht:** Sperre auf Elternblock oder ganzen Ast. Bei Bullets auf oberster Ebene wäre das die ganze Notiz, und Arbeit tief im Ast würde Kollegen im selben Ast ausschließen.
- **Mobil und Hintergrund:** Bei `visibilitychange` (Seite verborgen) und `pagehide` sendet der Client die Warteschlange mit `fetch(…, { keepalive: true })` und gibt Leases frei. Bei `online` und beim Zurückkehren in die App synchronisiert er sofort.
- **Anzeige:**
- Gesperrte Bullets zeigen dezent Avatar und Namen.
@@ -287,6 +292,7 @@ Phase 2 ist bewusst vor den Push-Kanälen: Steht die Bearbeitung über reine HTT
| Frage | Entscheidung |
|---|---|
| Lease-Dauer | 20 s, Verlängerung alle 7 s beim Tippen |
| Lease-Umfang | Pro Block, nicht pro Ast. Neue Bullets automatisch gesperrt für den Ersteller, 20 s Nachlauf nach Verlassen, Tippen wartet nie auf die Sperre. |
| Konflikte | Konfliktkopie als eigener Block darunter, mit Übernehmen/Verwerfen. Kein automatisches Zusammenführen. |
| Titel | Jeder Bearbeiter darf ihn ändern; Bearbeiter haben außer Freigaben und Löschen volle Rechte. |
| Verlauf/Undo | Strg+Z/Wiederholen für eigene Änderungen ist Teil des Features (Phase 3). Eine Verlaufsansicht kommt später. |