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
+16 -115
View File
@@ -131,30 +131,10 @@
| [?] #be.auth.token: Single user, one token (S)
| [?] #be.auth.oidc: Log in with OIDC (L)
- [~] #col: Collaborating on a Diagram (XXL)
" The pad merges concurrent edits, Werkbaum renders the plan.
- [^] #col.pad: Watch a shared Etherpad — it merges, we render (L)
- [^] #col.pad.export: Load the pad's plain-text export (XS)
- [^] #col.pad.reload: Reload on demand (XS) %% a timer only trips Etherpad's rate limit
- [^] #col.pad.embed: Embed the pad, keep the text mirror beside it (M)
- [^] #col.pad.views: Three views: pad and text, pad only, text only (S)
- [^] #col.pad.split: Splitter between pad and mirror (XS)
- [?] #col.pad.cookie: Editing in the frame stops after a while (S) %% SameSite=Lax, see D31
| [?] #col.pad.samesite: Ask the admin for cookie.sameSite = None (XS)
| [ ] #col.pad.owntab: Live with it: edit in the pad's own tab (XS)
- [^] #col.pad.point: A shared pointer: !!! focuses a node for everyone (XS)
- [?] > #col.auto: Update by itself, within about 2 s (M)
| [?] #col.own: Our own Etherpad beside Werkbaum (L) %% then every setting below is ours
- [ ] #col.own.db: Node and a real database in userland (S) %% the flatfile store is not for production
- [ ] #col.own.ram: Book the RAM quota for an own daemon (XS)
- [ ] #col.own.proxy: Reverse proxy in .htaccess, websockets included (S) %% Mattermost does it, wiki shows how
- [ ] #col.own.unit: systemd user unit so it survives a reboot (XS)
- [!] #col.own.patch: Keeping a public service patched (S) %% indefinitely, that is the real cost
| [?] #col.auto.limit: Admin raises the export rate limit (XS) %% default is 10 per 90 s
| [?] #col.auto.plugin: Admin installs a plugin that pushes the text (S) %% postMessage to the page
| [?] #col.auto.relay: Our own backend holds one socket and fans out (XXL) :#be.scaffold
| [ ] #col.auto.poll: Poll inside the budget, at best every 9 s (S) %% legal, but not 2 s
| [-] #col.auto.socket: Our own socket straight to the pad (L) %% measured: upgrade refused
| [-] #col.auto.html: Read the pad's HTML instead of the export (M) %% measured: no CORS header
" Reading together is solved, changing together too — typing together at the
" same instant is the expensive one.
- [^] #col.point: A shared pointer: !!! focuses a node for everyone (XS)
- [-] #col.pad: Borrow an Etherpad — it merges, we render (L) %% built, then removed, see D78
- [ ] #col.git: Git as the shared store (L)
| [ ] #col.git.pr: A file in a repository, changed by pull request (S) %% works today, no code
| [?] #col.git.auto: The backend commits every change (L) :#be.scaffold
@@ -714,8 +694,8 @@
#bld.pwa.sw
A worker lets the editor start without a network. It is deliberately dumb:
navigations go network-first, and the cache only keeps the last version it
has seen of the one self-contained file for the offline case. Texts pulled from a URL
or a pad pass through untouched and stay online by their nature.
has seen of the one self-contained file for the offline case. Texts pulled from
a URL or a server pass through untouched and stay online by their nature.
#bld.pwa.update
The feared migration turned out unnecessary: because the worker only
@@ -838,96 +818,17 @@
machinery. Reading together is solved, changing together mostly is, and
typing together at the same instant is the expensive one.
#col.point
Nobody sees anyone else's caret, so a mark in the text is the only way to
point at a place — and it has something a caret has not: everyone sees the
same spot.
#col.pad
Let an Etherpad do the hard part: it merges concurrent edits, and Werkbaum
only renders the result. Borrowed rather than rebuilt.
#col.pad.export
Every pad offers its content as plain text, with the header that lets a
foreign page read it. That one endpoint is the entire integration.
#col.pad.reload
Fetching happens on a button press, not on a timer. The export is rate
limited, and a timer does nothing but trip it.
#col.pad.embed
The pad in a frame inside the editor, with our own text mirror beside it.
The mirror keeps the jumps between diagram and text working, because a
foreign frame gives up no access to its content.
#col.pad.views
Pad and text, pad only, text only. Text only is the view that tells the pad
nothing about you — a loaded pad announces you in its list of people.
#col.pad.split
A splitter of its own between frame and mirror, remembered separately for
side by side and stacked.
#col.pad.cookie
In a foreign frame the pad's author cookie is not sent, so a session can
turn read-only after a while. Not fixable from this side.
#col.pad.samesite
One server setting resolves it. Presumes somebody who can change that
setting.
#col.pad.owntab
Or live with it: read along in the frame, and write in the pad's own tab
where the cookie works.
#col.pad.point
Nobody sees anybody else's cursor across two applications, so a mark in the
text is the only way to point at something. It has one thing a cursor
hasn't: everyone sees the same spot.
#col.auto
The view should follow the pad by itself, within a couple of seconds. Every
route below was measured rather than guessed at.
#col.own
Running the pad ourselves makes every setting below it ours. It also makes
every one of them our job.
#col.own.db
The default file store is not meant for production, so a real database and a
runtime to host it are the price of admission.
#col.own.ram
A daemon of one's own needs memory booked for it.
#col.own.proxy
Reaching it needs a reverse proxy that passes websockets through as well.
#col.own.unit
And something that starts it again after a reboot.
#col.own.patch
The real cost is not the setup but keeping a public service patched,
indefinitely.
#col.auto.limit
The cheapest effective change: raise the export limit on the pad server. Ten
fetches per ninety seconds is the default, and the reason a timer fails.
#col.auto.plugin
A pad plugin can push the text to the embedding page. Live, no polling, no
limit touched — and nothing to install on our side.
#col.auto.relay
Our own backend holds one connection to the pad and fans out to every
viewer. Solves it for everybody at once, and costs a backend.
#col.auto.poll
Staying inside the budget means one fetch every nine seconds at best, shared
by everyone behind the same address. Legal, but not two seconds.
#col.auto.socket
Measured and rejected: the pad server refuses the websocket upgrade when it
comes from another origin.
#col.auto.html
Measured and rejected: the pad page carries the full text, but sends no
header that would let a foreign page read it.
Removed. An Etherpad did the hard part for a while: it merged concurrent
edits and Werkbaum only rendered the result, borrowed rather than rebuilt.
The borrowing is what it cost — a rate-limited export, no writing from here,
and an author cookie that a foreign frame never gets. The backend does it
better and in our own editor, so this went out again.
#col.git
Keep the plan in a repository. Because it is text, diff, blame, review and