frontend: ?etherpad= — Echtzeit-Zusammenarbeit über ein geliehenes Pad

Werkbaum hat kein Backend, und das eigentlich Schwere an gemeinsamem
Bearbeiten ist das Zusammenführen gleichzeitiger Änderungen — im Plan als
`[!] Merging simultaneous edits` markiert. Etherpad hat das gelöst. Also
geliehen statt nachgebaut: das Pad ist die Schreibfläche, Werkbaum die
Ansicht.

Nachgemessen an pad.hostsharing.net, bevor irgendwas gebaut wurde:
- der Klartext-Export sendet `Access-Control-Allow-Origin: *`,
- das kanonische Beispiel (SPEC §10) kommt byte-identisch zurück,
- der HTML-Export zeigt kein Listen-Markup — Etherpad deutet `-` nicht zur
  Aufzählung um und behält die führenden Leerzeichen.
Das war das Risiko, das die Idee hätte erledigen können. Nicht geprüft ist
das Tippen im Etherpad-Editor selbst (Tab-Einrückung, mögliches
Auto-Bullet); dafür braucht es einen echten Browser.

Eigener Parameter statt `?sourceUrl=`: Die URL, die ein Mensch in der Hand
hat, ist die Pad-URL — `/export/txt` hängt Werkbaum selbst an. Vor allem
aber lizenziert der eigene Parameter das andere Verhalten, sodass D23
unangetastet bleibt: `sourceUrl` heißt weiter „statische Datei, einmal pro
Laden geholt", bestehende Links bekommen kein Polling. Name und id sind die
vollständige Pad-URL (Pad-Namen sind nur pro Instanz eindeutig).

Textfeld schreibgeschützt, Knopf öffnet das Pad: ohne das verschwände
getippter Text beim nächsten Abruf. Schrift bleibt Tinte statt grau — hier
wird gelesen, der Plantext ist der Hauptinhalt.

Drei Riegel im Takt, jeder aus einem echten Fehler:
- `padBusy` — im Netzwerk-Mitschnitt stapelten sich die Abrufe, weil die
  Gegenseite langsamer war als der Takt; eine spät eintreffende alte
  Antwort hätte neueren Text überschrieben,
- Abbruch nach 10 s — sonst bliebe der Riegel bei hängender Gegenseite für
  immer zu,
- `visibilityState` + `visibilitychange` — nicht im Hintergrund abrufen,
  aber bei Rückkehr sofort.
Der Stabilitätstakt übernimmt erst beim zweiten gleichen Abruf, sonst sieht
man die anderen mitten im Tippen.

Die Normalisierung der Pad-Adresse liegt headless in `remote.js`, damit sie
testbar ist (23 neue Tests, 60 -> 83). Im Vorschau-Browser meldet
`visibilityState` „hidden" und HMR lädt bei jeder Quelländerung neu — ein
Reload sieht wie eine geglückte Übernahme aus; nachgewiesen wurde die
Übernahme deshalb mit einem Marker auf `window`, der einen Reload nicht
überlebt.

SPEC §9 zuerst, dann D31, dann Code. Der Plan bekommt den Knoten nach D30
mit `[x]`, nicht `[^]`.
This commit is contained in:
mhoennig
2026-07-30 12:04:22 +02:00
parent cfbbfab54b
commit 3310cab7be
11 changed files with 478 additions and 31 deletions
+20
View File
@@ -135,6 +135,26 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
Endung `.werkbaum` ist reine Konvention (D24, SPEC §12). Beispieldateien zum
Ausprobieren: `docs/examples/*.werkbaum` (nacheinander geöffnet ergeben sie
mehrere Dokumente im Wähler).
- `?etherpad=` (D31): `remoteSource()` liefert **einen** Beschreiber für beide
Eingänge (`?sourceUrl=` und `?etherpad=`), `loadRemoteSource()` holt und legt
das Dokument an — ein Fetch-Pfad, ein Warnkanal. Die Normalisierung der
Pad-Adresse steht headless in **`remote.js`** (`padUrls`, Tests in
`tests/remote.test.js`): Export-/Timeslider-Pfad, Query, Fragment und
Schrägstriche fallen weg, verlangt wird `/p/<name>` am Ende. Name und id sind
die **vollständige** Pad-URL (Pad-Namen sind nur pro Instanz eindeutig).
`padSource` muss **vor** `loadActiveIntoEditor()` gesetzt werden — daran hängt
der Schreibschutz. `pollPad()` hat drei Riegel, jeder aus einem echten Fehler:
`padBusy` (höchstens ein Abruf unterwegs — sonst stapeln sich Anfragen und eine
spät eintreffende alte Antwort überschreibt neueren Text, im
Netzwerk-Mitschnitt beobachtet), `PAD_FETCH_TIMEOUT_MS` (ohne Abbruch bliebe
`padBusy` bei hängender Gegenseite für immer zu) und `visibilityState`
(+ `visibilitychange`-Handler, der bei Rückkehr sofort holt). Fehlschläge im
Takt bleiben **stumm**; nur der erste Ladeversuch warnt. Der Stabilitätstakt
(`padPending`) übernimmt erst beim zweiten gleichen Abruf — sonst sieht man die
anderen mitten im Tippen. Beim Prüfen im Vorschau-Browser: `visibilityState`
ist dort `hidden` (Polling also aus) und HMR lädt bei jeder Quelländerung neu —
ein Reload sieht wie eine geglückte Übernahme aus. Marker auf `window` setzen
und hinterher prüfen, sonst beweist der Test nichts.
- Sprung Diagramm ↔ Text (D25): `render.js` schreibt die Parser-Zeilennummer als
`data-line` an jeden Knoten (Geister-Knoten bekommen keine). `jumpToLine()` in
`app.js` klappt bei Bedarf das Editor-Panel auf (`revealEditor()`), markiert die