feat(frontend): gemeinsam am Server-Dokument arbeiten (?live=, Schritt 6)
Der Editor fuehrt ein Dokument des Backends: laden, nach 1,5 s Ruhe das Diff schicken, ueber einen offenen Abruf fremde Aenderungen einspielen — ohne Neuladen, mit mitwandernder Schreibmarke. Dazu CORS im Backend; ohne das blockiert der Browser jeden Aufruf. Zwei Fehler hat erst der Live-Test gegen das laufende Backend gefunden, beide an der Naht zwischen Modul und Verdrahtung (D54-Nachtrag 3): Der Konflikt entstand nie. Mit laufendem Feed zieht die Schattenkopie staendig nach, die eigene Basis ist also nie veraltet — der Server haette nie 409 geantwortet, und die fremde Zeile waere stillschweigend ueberschrieben worden. Der Client prueft die Ueberschneidung jetzt selbst gegen den ungesendeten Text; den kennt der Server nicht. Die Nummer begann nach jedem Neuladen wieder bei 1, waehrend die Kennung blieb — der Server hielt die erste echte Aenderung fuer eine Wiederholung und tat nichts. Beides liegt jetzt im sessionStorage: je Tab, ueberlebt Neuladen. Je Tab ist zugleich die richtige Aussage, zwei Tabs sind zwei Schreiber. Nachgemessen im Browser: fremde Aenderung erscheint ohne Neuladen, Getipptes erreicht den Server, beide Konflikt-Knoepfe tun was sie sagen, und die Schreibmarke steht nach zwei fremd eingefuegten Zeilen darueber unveraendert bei Zeile+2, Spalte 8. 518 Frontend-Tests, 135 im Backend. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5881be1a2c
commit
08965157a1
@@ -17,6 +17,12 @@ the git history of `docs/examples/werkbaum.werkbaum`. A day can therefore carry
|
||||
a link without having a note (someone forgot to write one) — but never the
|
||||
reverse.
|
||||
|
||||
## 2026-08-26
|
||||
|
||||
- Plans can live on a Werkbaum backend now: open `?live=<document URL>` and everyone edits the same text, seeing each other's changes without reloading
|
||||
- When two people change the same lines, a bar asks whose version should win — everything else the server merges by itself
|
||||
- The caret stays where you put it when someone else inserts lines above you
|
||||
|
||||
## 2026-08-25
|
||||
|
||||
- The fold button cycles through four presets now: size M and smaller, everything off the cheapest path, everything, all open
|
||||
|
||||
@@ -6196,3 +6196,69 @@ gehasht wurde, ein Wechsel des Verfahrens bricht nichts — und die Tests dürfe
|
||||
ausdrücklich vorgesehen („geprüft über Spring Security") und damit von der
|
||||
Rückfragepflicht der Wurzel-CLAUDE.md gedeckt. Sie ist zugleich der Platz für
|
||||
die spätere richtige Authentifizierung.
|
||||
|
||||
**Nachtrag 7 — der Client: was der Live-Test gefunden hat (2026-08-26).**
|
||||
Schritt 6 steht: `?live=<Dokument-URL>` führt ein Server-Dokument, schickt nach
|
||||
1,5 s Ruhe das Diff und hält einen Feed offen. Die entscheidbare Hälfte liegt
|
||||
headless in `frontend/src/live.js`, die I/O in `app.js` (Hausregel,
|
||||
D54-Nachtrag 3). Fünf Festlegungen und zwei Funde:
|
||||
|
||||
**Der Name ist der Titel des Servers, nicht die URL.** Das weicht vom
|
||||
Haupttext ab („der Name ist die URL", wie bei `?sourceUrl=` und `?etherpad=`),
|
||||
und zwar mit Grund: Anders als eine Datei oder ein Pad **hat** ein
|
||||
Server-Dokument einen Namen, und alle sehen denselben. Die vollständige
|
||||
Adresse steht wie dort im Tooltip. Identität und Wiederfinden hängen
|
||||
unverändert an der URL.
|
||||
|
||||
**Konflikte entstehen beim Tippen, nicht erst beim Senden — und das war der
|
||||
erste Fund.** Der Server sieht nur, was eingereicht wird; den ungesendeten
|
||||
Text im Editor kennt er nicht. Mit laufendem Feed zieht die Schattenkopie
|
||||
ständig nach, die eigene Basis ist also nie veraltet — ein 409 käme praktisch
|
||||
nie zustande, und die fremde Zeile wäre **stillschweigend überschrieben**.
|
||||
Deshalb prüft der Client beim Einblenden fremder Änderungen selbst, ob sie
|
||||
sich mit dem gerade Getippten überschneiden, und stellt dann dieselbe Frage.
|
||||
Ein Zustand, zwei Wege hinein: lokal erkannt oder vom Server gemeldet.
|
||||
|
||||
**Die Frage lautet „wessen Fassung", nicht „welche Zeile".** Zwei Knöpfe,
|
||||
ganzes Dokument: *Fremde übernehmen* setzt den Text auf den Server-Stand,
|
||||
*Eigene durchsetzen* zieht nur die Schattenkopie nach und schickt den eigenen
|
||||
Text darauf. Zeilenweises Zusammenführen im Konfliktfall wäre eine eigene
|
||||
Oberfläche; verloren geht dabei ohnehin nichts — der verworfene Stand liegt in
|
||||
den früheren Ständen (D54) und jede Version in der Historie des Servers.
|
||||
Solange die Frage offen ist, ruhen Senden **und** Feed: Sonst zöge der Stand
|
||||
unter der Frage weg, die gerade gestellt ist.
|
||||
|
||||
**Kennung und laufende Nummer liegen im sessionStorage, also je Tab — das war
|
||||
der zweite Fund.** Erst lag beides im localStorage. Nach einem Neuladen begann
|
||||
`seq` wieder bei 1, die Kennung blieb — und der Server hielt die erste echte
|
||||
Änderung für die Wiederholung der letzten von vorhin und tat **nichts**. Im
|
||||
Live-Test sofort sichtbar, in keinem Unit-Test: Genau die Naht zwischen Modul
|
||||
und Verdrahtung, vor der D54-Nachtrag 3 warnt. Je Tab ist zugleich die
|
||||
richtige Aussage: Zwei Tabs sind zwei Schreiber; mit gemeinsamer Nummer
|
||||
schickte der eine bald eine kleinere `seq` als der andere.
|
||||
|
||||
**Fremde Änderungen werden nicht undo-fähig eingespielt.** Ein Strg+Z, das den
|
||||
Beitrag eines anderen zurücknimmt, wäre eine Lüge über die Herkunft. Die
|
||||
Schreibmarke wandert dagegen mit (`mapLine`) — ohne das spränge sie bei jeder
|
||||
fremden Änderung weiter oben im Dokument, und „kein Neuladen" wäre nichts
|
||||
wert.
|
||||
|
||||
**CORS steht auf `*`, und das ist hier keine Nachlässigkeit.** Der Editor läuft
|
||||
je nach Installation überall (Pages, eigene Domain, Dev-Server); Zugriff regelt
|
||||
die unerratbare UUID, nicht die Herkunft, und Cookies werden nie mitgesendet
|
||||
(`credentials: omit`). CORS schützt Anmeldedaten — die es hier nicht gibt. Wer
|
||||
es enger will, setzt `werkbaum.cors.allowed-origins`.
|
||||
|
||||
**Nachgemessen im Browser gegen das laufende Backend**, weil das der einzige
|
||||
Ort ist, an dem sich das beweisen lässt: Ein Server-Dokument lädt und rendert;
|
||||
eine fremde Änderung erscheint ohne Neuladen; Getipptes erreicht nach 1,5 s
|
||||
den Server; zwei Änderungen an derselben Zeile öffnen das Konflikt-Band, und
|
||||
beide Knöpfe tun, was sie sagen; die Schreibmarke steht nach zwei fremd
|
||||
eingefügten Zeilen darüber unverändert bei Zeile+2, Spalte 8. **Werkzeuggrenze,
|
||||
wie in D25 und D17-Nachtrag 4:** Der Automatisierungs-Tab meldet sich dauerhaft
|
||||
als `document.hidden` — die Sichtbarkeits-Sperre des Feeds (Nachtrag 1) greift
|
||||
also nachweislich, ließ sich aber nur mit gestellter Sichtbarkeit umgehen, um
|
||||
alles Übrige zu sehen.
|
||||
|
||||
**Offen bleibt** `PATCH /title` (und damit das Ereignis `RENAMED`), ein
|
||||
Eingabefeld für den Anzeigenamen und die Präsenz-Anzeige.
|
||||
|
||||
@@ -818,6 +818,29 @@ Export- oder `/timeslider`-Pfad wird abgeschnitten.
|
||||
|
||||
Siehe D31.
|
||||
|
||||
### Gemeinsam an einem Server-Dokument arbeiten (`?live=`)
|
||||
Liegt der Plan auf einem Werkbaum-Backend, nimmt der Editor dessen
|
||||
Dokument-Adresse: `…?live=https://example.org/api/v1/documents/<uuid>`.
|
||||
Anders als beim Pad (`?etherpad=`) wird hier **im Editor selbst geschrieben**,
|
||||
und alle sehen die Änderungen der anderen, ohne neu zu laden.
|
||||
|
||||
- **Das Textfeld bleibt beschreibbar.** Nach kurzer Ruhe (1,5 s) schickt der
|
||||
Editor die Änderung als Zeilen-Diff; ein offener Abruf hält die Gegenrichtung
|
||||
bereit und spielt fremde Änderungen ein. Die **Schreibmarke wandert mit** —
|
||||
fügt jemand oberhalb Zeilen ein, bleibt sie an ihrer Stelle im Text.
|
||||
- **Der Name ist der Titel des Dokuments** (alle sehen denselben), die
|
||||
vollständige Adresse steht im Tooltip.
|
||||
- **Überschneiden sich zwei Änderungen wirklich** — dieselben Zeilen —, fragt
|
||||
ein Band oben, wessen Fassung gelten soll: *Fremde übernehmen* oder *Eigene
|
||||
durchsetzen*. Alles andere führt der Server selbst zusammen, ohne zu fragen.
|
||||
Verloren geht nichts: Der verworfene Stand bleibt in den früheren Ständen,
|
||||
jede Version in der Historie des Servers.
|
||||
- Der Abruf läuft **nur im sichtbaren Tab**; im Hintergrund ruht er und holt
|
||||
beim Zurückkommen den Rückstand.
|
||||
|
||||
Siehe D76 (Protokoll und Begründung) und
|
||||
`backend/docs/live-editing-proposal.md`.
|
||||
|
||||
### Legende im Editor-Panel
|
||||
Neben dem Textfeld steht eine aufklappbare **Legende** (Notation in Kurzform,
|
||||
abschließend eine Bedienungs-Zeile). Sie ist **scrollbar**, wenn ihr Inhalt
|
||||
|
||||
Reference in New Issue
Block a user