feat: Etherpad-Anbindung ausgebaut (D78)

D31 lieh sich fuer die Echtzeit-Zusammenarbeit ein Etherpad: Das Pad war die
Schreibflaeche, Werkbaum die Ansicht. Richtig fuer den Zeitpunkt - die schwere
Arbeit war dort getan, und Werkbaum hatte kein Backend. Jetzt hat es eins
(D76), das dasselbe besser und im Editor selbst kann.

D31 bleibt stehen: Was dort gemessen wurde, ist der Grund, warum die Anbindung
nie gut wurde - Export-Drosselung (10 je 90 s), SameSite=Lax beim
Autoren-Cookie, abgelehnter Socket-Upgrade. Uebrig blieb ein Neu-laden-Knopf
und ein schreibgeschuetztes Textfeld.

Ein alter ?etherpad=-Link meldet sich, statt still nichts zu tun: Der Parameter
bleibt erkannt und ergibt die zeilenlose Warnung padGone, die auf ?live= zeigt
(neun Sprachen). Geholt wird nichts - nachgemessen geht keine Anfrage mehr an
den Pad-Host. Vorhandene Pad-Dokumente bleiben liegen und werden dabei besser:
ohne Schreibschutz sind sie bearbeitbar und sammeln fruehere Staende.

Mit ausgebaut, weil sie nur der Anbindung dienten: remote.js samt Tests, der
Ansichts-Waehler und sein Splitter, der Neu-laden-Knopf samt
Drosselungs-Zaehler, die Warnungen sourceTimeout/padRateLimit, elf i18n-
Schluessel x 9 Sprachen - und src.readOnly ganz, denn nur Pad-Dokumente haben
es gesetzt (mit ihm updateSnapBtn und sieben tote Waechter). Auch #srcArea
faellt weg: den Kasten gab es nur, damit Pad-Rahmen und Spiegel sich einen
Bereich teilen. ?sourceUrl= bleibt unangetastet - beide teilten sich einen
Fetch-Pfad, das war die Stelle, an der nichts verrutschen durfte.

Im Plan wird aus dem #col.pad-Zweig (14 Knoten samt der ganzen
"Update by itself"-Gruppe) ein verworfener Knoten mit Begruendung; der
gemeinsame Zeigefinger wandert als #col.point heraus, denn !!! gilt jedem
geteilten Dokument. 196 Knoten, 0 Warnungen.

Nebengewinn: Der README bekommt endlich den Abschnitt zu ?live= - den gab es
nie, die Einstiegs-Beschreibung stand weiter beim Pad.

Nachgemessen im Browser: ?etherpad= zeigt die Warnung ohne Netz-Anfrage,
?sourceUrl= laedt unveraendert und ist beschreibbar, Textfeld und
Zahlenstreifen liegen buendig, Legenden-Splitter teilt wie zuvor (954+10+300),
Falten und Staende funktionieren. 501 Tests, 946 Zeilen weniger.
This commit is contained in:
mhoennig
2026-08-26 20:31:33 +02:00
parent 911a100df4
commit bfacbe96e5
17 changed files with 267 additions and 934 deletions
+28 -39
View File
@@ -83,57 +83,46 @@ http(s) regardless of extension or content type.
do; an arbitrary web server often does not. If loading fails the previous
content stays and a warning explains why. Only `http`/`https` are allowed.
### Working on one plan together (Etherpad)
### Working on one plan together (`?live=`)
For real-time collaboration Werkbaum needs no backend of its own — it borrows an
**Etherpad**. Pass the pad address as it appears in your browser (no export path,
Werkbaum appends that itself):
If the plan lives on a Werkbaum backend, everyone edits it **in the editor
itself** and sees the others' changes without reloading:
```
https://werkbaum.javagil.de/?etherpad=https://pad.hostsharing.net/p/my-plan
https://werkbaum.javagil.de/?live=https://werkbaum.javagil.de/api/v1/documents/<uuid>
```
Everyone edits the notation text **in the pad**; a reload button next to the pad
button fetches the current state. Merging simultaneous edits is Etherpad's job —
that is the whole point.
To create such a document, open the document menu and pick **"Put on the
server"**: it uploads the active plan, switches to it, and puts the link in the
address bar and on your clipboard. Share that link — the address is the
invitation, and knowing it is what grants access.
Deliberately **no background polling**: Etherpad rate-limits the export (10
fetches per 90 s per IP by default), so a timer does not win against it — it
*causes* the throttling. Measured: after exceeding the budget the server holds
the connection open with no reply for about two minutes, then answers in 0.4 s.
The button pairs well with "what's new": press it, and whatever went into
production since your last look lights up.
The text area stays **writable**. After 1.5 s of quiet the editor sends the
change as a line diff; an open request holds the other direction ready and plays
foreign changes in. **The caret travels with them** — if someone inserts lines
above you, it stays where it was in the text.
Because the pad is the writing surface, the text area here is **read-only**; a
button in the editor title bar opens the pad in a new tab. Without that
protection, anything you typed would vanish on the next fetch.
The document's **title** is its name (everyone sees the same one), the full
address sits in the tooltip.
Verified against Etherpad: the plain-text export sends
`Access-Control-Allow-Origin: *`, and the notation comes back **byte-identical**
leading spaces, `-`/`+`/`|`, status boxes and `%%` survive Etherpad's storage
model, and `-` is not turned into a bullet list.
The pad can also be **embedded** in the editor panel: a selector in the title bar
cycles through *pad and text* (split by a draggable divider), *pad only* and *text
only*. The narrow text mirror keeps its purpose — the jump between diagram and
text works on it, and in *pad only* a jump brings it back by itself. The frame is
only loaded while it is visible, because a loaded pad connects and shows you in
the pad's list of people.
When two changes really overlap — the same lines — a bar at the top asks whose
version should win: *take theirs* or *keep mine*. Everything else the server
merges without asking. Nothing is lost either way: the discarded state stays in
the earlier states, and every version is in the server's history.
**A shared pointer:** write `!!!` on a line and that node is highlighted and
scrolled into view — for **everyone** looking at the pad, which is something a
cursor cannot do. Recognised only as a standalone token, so `Careful!!!` stays an
ordinary label. It stays in the text until someone deletes it.
scrolled into view — for **everyone** looking at the document, which is
something a cursor cannot do. Recognised only as a standalone token, so
`Careful!!!` stays an ordinary label. It stays in the text until someone
deletes it.
**Be aware:** your plan text then lives on third-party infrastructure, and a pad
is readable by anyone who knows its address. In an embedded frame Etherpad's
author cookie (`SameSite=Lax`) is not sent, so you count as a new author on every
load — fixable only on the server (`cookie.sameSite: "None"`).
Setting up the backend: see [backend/README.md](backend/README.md) and
`docs/DECISIONS.md` D76.
For the same reason the **embedded frame is best for reading along**: if editing
in it ever stops working, cycle the view selector once (that reloads the frame),
or use the "edit in the pad" button and work in the pad's own tab, where the
cookie does apply. See `docs/DECISIONS.md` D31 and D32.
> Werkbaum used to borrow an **Etherpad** for this (`?etherpad=`). That is
> removed — the backend does the same job better and in the editor itself. An
> old `?etherpad=` link now shows a note pointing here rather than silently
> doing nothing; see `docs/DECISIONS.md` D78.
### Running it locally