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
+1
View File
@@ -19,6 +19,7 @@ reverse.
## 2026-08-26
- The Etherpad integration is gone: collaboration now runs through a Werkbaum backend, and an old `?etherpad=` link says so instead of doing nothing
- 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
+84
View File
@@ -6660,3 +6660,87 @@ Version), `frontend info` (1.1.162, Commit = HEAD), `backend status`,
`backend restart`, `backend backup` samt lokalem Wiederanlauf,
`frontend preview` (nichts geschrieben, HEAD unverändert) und die
Fehlerpfade (Exit-Code 2 bei unbekanntem Ziel und fehlender Aktion).
## D78 — Die Etherpad-Anbindung ist ausgebaut
D31 lieh sich für die Echtzeit-Zusammenarbeit ein **Etherpad**: Das Pad war die
Schreibfläche, Werkbaum die Ansicht. Das war die richtige Entscheidung für den
Zeitpunkt — die schwere Arbeit (gleichzeitige Änderungen zusammenführen) war
dort seit Jahren getan, und Werkbaum hatte kein Backend. **Jetzt hat es eins**
(D76), und das kann dasselbe besser und im Editor selbst. Also raus damit,
statt zwei Wege zur selben Sache zu pflegen.
**D31 bleibt stehen** — Entscheidungen werden nicht gelöscht, sondern
fortgeschrieben. Was dort gemessen wurde, gilt weiter und ist der Grund, warum
die Anbindung nie gut wurde: Etherpad **drosselt den Export** (serienmäßig 10
Abrufe je 90 s und IP), ein Hintergrund-Takt erzeugt die Drosselung, statt sie
zu umgehen; das Autoren-Cookie ist `SameSite=Lax` und kommt im eingebetteten
Rahmen nicht an, also ist man bei jedem Laden ein neuer Autor; und ein eigener
Socket zum Pad wurde von der Gegenseite abgelehnt (D31-Nachtrag, Code 1006).
Übrig blieb ein Neu-laden-Knopf und ein schreibgeschütztes Textfeld — die
Rückmeldung „funktioniert sowieso nicht gut" ist die ehrliche Zusammenfassung.
**Was `?live=` besser macht**, in derselben Reihenfolge: Es wird **im Editor
geschrieben** statt in einem fremden Rahmen; die Gegenrichtung ist ein offener
Abruf statt eines Takts gegen ein fremdes Limit; der Konflikt wird benannt
statt versteckt; und der Plantext liegt auf **eigener** Infrastruktur statt auf
einem Pad, das jeder lesen kann, der die Adresse kennt.
**Ein alter Link meldet sich, statt still nichts zu tun.** `?etherpad=` bleibt
als **erkannter** Parameter stehen und ergibt die zeilenlose Warnung `padGone`,
die auf `?live=` zeigt — in allen neun Sprachen. Der Parameter war geteilt: Wer
ihn irgendwo stehen hat (in einer Mail, einem Wiki, einem Lesezeichen), bekäme
sonst ein leeres Werkbaum ohne Erklärung. Genau der stille Fehler, den dieses
Projekt sonst überall ablehnt (SPEC §4, D59). Es ist bewusst **kein**
Rest-Feature: Geholt wird nichts, der Pad-Host sieht keine Anfrage mehr
(nachgemessen).
**Vorhandene Pad-Dokumente bleiben liegen — und werden dabei besser.** Sie sind
im localStorage gewöhnliche Dokumente (`{id, name, text, source}`); ohne den
Schreibschutz sind sie ab jetzt **bearbeitbar**, sammeln frühere Stände (D54)
und lassen sich falten. Ihr Text ist der zuletzt geholte. Nichts wird gelöscht,
niemand verliert seinen Plan.
**Der Schreibschutz verschwindet ganz, nicht nur seine Ursache.** `src.readOnly`
wurde ausschließlich von Pad-Dokumenten gesetzt; danach hätten sieben Wächter
in Falten, Kurz-IDs, Autovervollständigung und Ständen auf eine Bedingung
geprüft, die nie mehr wahr wird — mit Kommentaren, die auf D31 zeigen. Tote
Wächter mit veralteter Begründung sind schlechter als keine; ein künftiger
Lesemodus braucht ohnehin seine eigene Entscheidung. Mit ihm fällt
`updateSnapBtn()` weg: Der Knopf ist jetzt immer da.
**Eine Layout-Ebene weniger.** `#srcArea` gab es nur, damit Pad-Rahmen und
Textspiegel sich einen Bereich teilen konnten, ohne die Legenden-Aufteilung
(D26) anzufassen. Ohne Rahmen ist es ein Kasten mit einem Kind. Nachgemessen
nach dem Entfernen: `.editor-body` trägt jetzt direkt `srcWrap` · `hintGutter` ·
`agenda`, und die drei kacheln die Breite exakt (954 + 10 + 300 px) — der
Legenden-Splitter arbeitet unverändert.
**Mit ausgebaut**, weil sie nur der Anbindung dienten: `remote.js` samt seinen
Tests (die Pad-Adressen normalisieren), der Ansichts-Wähler und sein Splitter
(`--pcol`/`--prow`), der Neu-laden-Knopf samt Drosselungs-Zähler, die Warnungen
`sourceTimeout` und `padRateLimit`, elf i18n-Schlüssel × 9 Sprachen und der
Timeout-Parameter von `fetchRemote()`. **`?sourceUrl=` bleibt unangetastet**
(D23) — beide teilten sich einen Fetch-Pfad, und das war die eine Stelle, an der
beim Schneiden nichts verrutschen durfte.
**Der Plan sagt es auch.** Im mitgelieferten `werkbaum.werkbaum` wird aus dem
`#col.pad`-Zweig (14 Knoten samt der ganzen „Update by itself"-Gruppe, die nur
Etherpads Grenze umgehen wollte) **ein** verworfener Knoten `[-] #col.pad` mit
Begründung — dieselbe Form, in der dort schon `#bld.ghpages` und `#not.store`
stehen. Der eine Knoten, der weiterlebt, wandert heraus: `#col.pad.point` wird
`#col.point`, denn der gemeinsame Zeigefinger `!!!` gilt jedem geteilten
Dokument, nicht nur einem Pad. Danach 196 Knoten, 0 Warnungen.
**Nebengewinn: Der README bekommt endlich den Abschnitt zu `?live=`.** Den gab
es nie — D76 ist gebaut und dokumentiert (SPEC §9, DECISIONS), aber die
Einstiegs-Beschreibung stand weiter beim Pad. Das Ausbauen hätte sonst ein Loch
hinterlassen; jetzt steht dort in beiden Sprachen, wie man ein Server-Dokument
anlegt, teilt und was bei einem Konflikt passiert.
**Nachgemessen** im Browser: `?etherpad=…` zeigt die Warnung, und im
Netzwerk-Mitschnitt geht **keine** Anfrage an den Pad-Host; `?sourceUrl=` lädt
unverändert und das Dokument ist beschreibbar (früher: schreibgeschützt); das
Textfeld liegt mit dem Zahlenstreifen bündig (1151 + 20 px), die Zeilennummern
sitzen auf ihren Höhen, Pfad und Stationen werden gezeichnet; der
Legenden-Splitter teilt wie zuvor. 501 Tests (die 24 Pad-Adress-Tests sind mit
`remote.js` gegangen, `padGone` ist dazugekommen).
+1 -1
View File
@@ -112,7 +112,7 @@ Tastendruck ein Commit); und wer bei einem Backend eigentlich was darf
Zu klären ist das Zusammenspiel mit der Update-Anzeige (D45): Sitzt ein
Worker vor der Seite, entscheidet **er**, was ausgeliefert wird — „der
laufende Build gegen den, den der Server schickt" ist dann nicht mehr
dasselbe. Dokumente aus `?sourceUrl=`/`?etherpad=` bleiben naturgemäß
dasselbe. Dokumente aus `?sourceUrl=`/`?live=` bleiben naturgemäß
online. Im Plan: `#bld.pwa`.
- Schlagworte `&tag` mit einer Filter-/Hervorheben-Linse im Diagramm: Der Baum
ist genau eine Zerlegung — ein Tag benennt eine Menge von Knoten quer dazu
+17 -61
View File
@@ -155,7 +155,7 @@ ab, auch außerhalb seines eigenen Teilbaums.
**Fokusmarke `!!!`** — „schau hier hin": Der Knoten wird im Diagramm
hervorgehoben und ins Bild geholt (§9). Gedacht für das gemeinsame Arbeiten an
einem Pad (§9, `?etherpad=`): Weil dort niemand den Cursor der anderen sieht,
einem Dokument (§9, `?live=`): Weil dort niemand den Cursor der anderen sieht,
ist eine Marke **im Text** der einzige Weg, auf eine Stelle zu zeigen — und sie
hat etwas, das ein Cursor nicht hat: **alle** sehen dieselbe Stelle.
@@ -770,59 +770,18 @@ Scheitert das Laden (häufigster Fall: das Ziel sendet keinen
`Access-Control-Allow-Origin`-Header, außerdem 404/Netzfehler), bleibt der
bisherige Stand stehen und es erscheint eine **Warnung**. Siehe D23.
### Gemeinsam an einem Pad arbeiten (`?etherpad=`)
Für Zusammenarbeit in Echtzeit nimmt der Editor die Adresse eines
**Etherpad-Pads** — die Adresse, die im Browser steht, ohne Export-Pfad:
`…?etherpad=https://pad.example.org/p/mein-plan`. Werkbaum hängt den
Klartext-Export (`/export/txt`) selbst an; ein versehentlich mitgegebener
Export- oder `/timeslider`-Pfad wird abgeschnitten.
- **Das Pad ist die Schreibfläche, Werkbaum die Ansicht.** Alle bearbeiten den
Notationstext im Pad, jeder Betrachter sieht das Diagramm mitwachsen. Das
Zusammenführen gleichzeitiger Änderungen macht Etherpad; Werkbaum tut es
nicht.
- Deshalb ist das Textfeld für ein solches Dokument **schreibgeschützt** — ein
Knopf in der Editor-Titelzeile öffnet das Pad im neuen Tab. Ohne den Schutz
verschwände getippter Text beim nächsten Abruf.
- Das Pad kann **im Editor-Panel eingebettet** werden. Ein Wähler in der
Titelzeile schaltet reihum zwischen drei Ansichten: **Pad und Text** (beide,
durch einen eigenen **Splitter** frei geteilt — Doppelklick setzt zurück),
**nur Pad** und **nur Text**. Die Aufteilung wird für nebeneinander und
gestapelt getrennt gehalten und bleibt erhalten.
- Der schmal gezogene Textspiegel behält seinen Zweck: Der Sprung zwischen
Diagramm und Text (§9) arbeitet auf ihm. In „nur Pad" ist er ausgeblendet —
ein Sprung holt ihn dann selbst zurück, so wie er ein zugeklapptes
Editor-Panel aufklappt.
- Der Rahmen wird **nur geladen, wenn er sichtbar ist**. Ein geladenes Pad
verbindet sich und zeigt dich in dessen Anwesenden-Liste; „nur Text" ist
damit die Ansicht, die nichts von dir verrät.
- Geholt wird **auf Knopfdruck**, nicht selbsttätig: Ein Neu-laden-Knopf neben
dem Pad-Knopf holt den aktuellen Stand. Etherpad **drosselt** den Export
(serienmäßig 10 Abrufe je 90 s und IP); ein Hintergrund-Takt läuft dagegen an
und bekommt am Ende gar nichts mehr. Läuft ein Abruf, dreht das Symbol — bei
gedrosselter Gegenseite kann das bis zum Abbruch (20 s) dauern.
- Der Knopf greift gut mit „Was ist neu?" (§9) zusammen: drücken, und was seither
in Produktion ging, leuchtet auf.
- **Name ist die vollständige Pad-URL** (nicht der bloße Pad-Name — zwei Pads
gleichen Namens auf verschiedenen Hosts wären sonst nicht zu unterscheiden),
wie bei `?sourceUrl=`. Identität und Name leiten sich von der **Pad**-Adresse
ab, nicht von der Export-Adresse — derselbe Pad ergibt damit genau ein
Dokument, gleich in welcher Schreibweise der Link kam.
- `?sourceUrl=` bleibt unverändert: statische Datei, einmal pro Laden geholt.
Der eigene Parameter trägt gerade den Unterschied.
- Fehler (CORS, 404, Netz) melden sich wie bei `?sourceUrl=`. Ein **Abbruch**
wegen Zeitablauf bekommt eine eigene Meldung, die die Drosselung benennt —
die `?sourceUrl=`-Meldung zeigt auf CORS und schickte hier auf die falsche
Fährte. Scheitert schon der erste Abruf, bleibt der Neu-laden-Knopf sichtbar
und holt es nach; ein Neuladen der Seite ist nicht nötig.
Siehe D31.
### Kein Etherpad mehr (`?etherpad=`, entfallen)
Der Editor konnte den Notationstext aus einem **Etherpad-Pad** beziehen: Alle
schrieben im Pad, Werkbaum zeigte das Diagramm dazu. Das ist **ausgebaut**
`?live=` (unten) kann dasselbe besser, und zwar im Editor selbst. Ein alter
`?etherpad=`-Link ergibt heute eine **Warnung**, die auf `?live=` zeigt, statt
still nichts zu tun. Siehe D78; die frühere Ausführung steht in 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.
Geschrieben wird **im Editor selbst**, 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
@@ -852,8 +811,7 @@ Ausrichtungen getrennt erhalten. Die Legende belegt höchstens 85 % des Panels,
damit das Textfeld nie ganz verschwindet. Siehe D26.
### Was ist neu? (Dokumente von außen)
Bei Dokumenten, die von außen kommen (mitgeliefert, per `?sourceUrl=` oder
`?etherpad=`), wird
Bei Dokumenten, die von außen kommen (mitgeliefert oder per `?sourceUrl=`), wird
gezeigt, was sich seit dem letzten Besuch getan hat. **„Neu" heißt: neu in
Produktion** — ein Knoten trägt jetzt `[^]` und tat es in der zuletzt gesehenen
Fassung nicht. Solche Knoten bekommen einen **gelben Strahlenkranz** nach außen
@@ -1018,8 +976,7 @@ eigene ID der Zeile.
weiter: Sie fängt keine Taste ab, solange sie nichts anzeigt, und Tab rückt
bei geschlossener Liste unverändert ein.
- **Kein Vorschlag** bei bloßem `#` (das *definiert* eine ID), im Kommentar
(`%%`), im Beschreibungsteil hinter `---` und in schreibgeschützten
Pad-Dokumenten (§9).
(`%%`) und im Beschreibungsteil hinter `---`.
- Der Parser sieht nie etwas davon; `llms.md` (§13) bleibt unberührt. Für
Screenreader meldet eine höfliche Live-Region die Trefferzahl und den
gewählten Eintrag; das Popup selbst ist `aria-hidden`, normales Tippen
@@ -1099,13 +1056,12 @@ fokussierten Knoten (WAI-ARIA-Baum-Idiom).
angefasst, solange die übrigen Marken den Zustand noch richtig beschreiben —
ein von Hand gesetztes `<` bleibt also stehen. Trifft es nicht mehr zu, werden
alle Marken neu gesetzt und das `<` dabei aufgelöst.
- Wo der Text **nicht beschreibbar** ist — bei einem Pad-Dokument (§9) —, gilt
der Eingriff **je Knoten** (Identität = Label-Pfad, wie bei „Was ist neu?“)
und **nur für die Sitzung**; ein Dokumentwechsel setzt ihn zurück. Dasselbe
gilt für einen Zustand, der sich in Marken gar nicht ausdrücken lässt (etwa
weil eine Fokusmarke `!!!` ihren Knoten immer wieder hervorholt): Dann wird
lieber nichts geschrieben, als einen Text zu hinterlassen, der etwas anderes
sagt als das Bild.
- Lässt sich ein Zustand in Marken **gar nicht ausdrücken** (etwa weil eine
Fokusmarke `!!!` ihren Knoten immer wieder hervorholt), wird lieber nichts
geschrieben, als einen Text zu hinterlassen, der etwas anderes sagt als das
Bild. Der Eingriff gilt dann **je Knoten** (Identität = Label-Pfad, wie bei
„Was ist neu?“) und **nur für die Sitzung**; ein Dokumentwechsel setzt ihn
zurück.
- **Für den ganzen Baum** gibt es im Diagramm-Kopf einen **Durchschalter**:
Jeder Druck stellt die **nächste** von vier Voreinstellungen her, reihum —
1. **ab Größe M abwärts zugeklappt**: jeder Knoten mit Kindern, dessen
+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