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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user