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:
@@ -979,3 +979,108 @@ einem absichtlich ausgeweiteten `sed` in einem Wegwerf-Worktree.
|
||||
bleibt eine bewusste Handlung. `deploy-prod.sh` warnt stattdessen, wenn HEAD
|
||||
noch nicht auf `origin` liegt: der Footer-Versionslink zeigt sonst auf einen
|
||||
Commit, den GitHub nicht kennt.
|
||||
|
||||
## D31 — Echtzeit-Zusammenarbeit über ein Etherpad, eigener Parameter `?etherpad=`
|
||||
Werkbaum hat kein Backend (D13 ist Plan, nicht Bestand), und der Plan setzt
|
||||
Zusammenarbeit als `[?] Live editing, several people at once (XL)` an — mit
|
||||
`[!] Merging simultaneous edits (L)` als der eigentlichen Arbeit. Genau diese
|
||||
Arbeit ist in Etherpad seit Jahren getan. Also wird sie geliehen statt
|
||||
nachgebaut: **Das Pad ist die Schreibfläche, Werkbaum die Ansicht.**
|
||||
|
||||
**Das Fundament war schon da.** Etherpad liefert pro Pad einen Klartext-Export
|
||||
(`/p/<pad>/export/txt`), und `?sourceUrl=` (D23) lädt jede Textdatei über
|
||||
http(s), ohne Endung oder `Content-Type` zu prüfen (D24). Nachgemessen an
|
||||
`pad.hostsharing.net`:
|
||||
|
||||
- `Access-Control-Allow-Origin: *` und `Content-Type: text/plain; charset=utf-8`
|
||||
— die eigentliche Hürde aus D23 (CORS) fällt also weg;
|
||||
- das kanonische Beispiel aus SPEC §10 kommt **byte-identisch** zurück
|
||||
(führende Leerzeichen, `-`/`+`/`|`, Statusboxen, `%%`, UTF-8);
|
||||
- der HTML-Export zeigt **kein** Listen-Markup: Etherpad speichert die
|
||||
Einrückung als echte Leerzeichen und deutet das `-` nicht zur Aufzählung um.
|
||||
Das war das Risiko, das die Idee hätte erledigen können.
|
||||
|
||||
(Gemessen wurde Import → Speicher → Export. Das *Tippen* im Etherpad-Editor —
|
||||
Tab-Einrückung, mögliches Auto-Bullet — ist damit **nicht** geprüft; dafür
|
||||
braucht es einen echten Browser. Vgl. die Lehre aus D25: synthetische Ereignisse
|
||||
beweisen nur die eigene Logik.)
|
||||
|
||||
**Eigener Parameter statt `?sourceUrl=`.** Drei Gründe, der erste ist der
|
||||
schwächste:
|
||||
|
||||
1. Die URL, die ein Mensch in der Hand hat, ist die **Pad**-URL — die aus der
|
||||
Adresszeile. `/export/txt` ist eine Implementierungseinzelheit und gehört
|
||||
nicht in die Schnittstelle; Werkbaum hängt sie selbst an.
|
||||
2. Der Parameter **lizenziert anderes Verhalten**. `sourceUrl` heißt „statische
|
||||
Datei, einmal pro Laden geholt" — das ist D23 wörtlich und bleibt
|
||||
unangetastet. `etherpad` heißt „lebendes Pad", und daran hängt das
|
||||
regelmäßige Abrufen. Ohne die Trennung müsste D23 seine Semantik ändern und
|
||||
bestehende Links bekämen ungefragt Polling.
|
||||
3. Die **Pad**-URL ist mehr wert als die Export-URL: nur mit ihr sind der
|
||||
„im Pad bearbeiten"-Knopf und ein späteres Einbetten (siehe unten) ohne
|
||||
weiteren Parameter erreichbar, und Identität/Name des Dokuments werden aus
|
||||
ihr gebildet — derselbe Pad ergibt so genau **ein** Dokument, auch wenn
|
||||
jemand versehentlich die Export-URL einträgt (sie wird normalisiert).
|
||||
|
||||
**Der Dokumentname ist die vollständige Pad-URL**, nicht der bloße Pad-Name.
|
||||
Kurz wäre schöner (`mein-plan` statt 45 Zeichen in einer schmalen Titelzeile),
|
||||
aber Pad-Namen sind nur **pro Instanz** eindeutig, nicht global: zwei Hosts mit
|
||||
je einem Pad `plan` ergäben zwei gleichnamige Dokumente im Wähler, ohne
|
||||
Möglichkeit sie zu unterscheiden. Damit gilt dieselbe Regel wie in D23 — und der
|
||||
Ellipsen-Schnitt in der Titelzeile samt vollständiger URL im Tooltip ist dafür
|
||||
schon eingerichtet.
|
||||
|
||||
Der Name `?etherpad=` statt des neutraleren `?pad=`: Das Anhängen von
|
||||
`/export/txt` **ist** produktspezifisch. Das ehrlich zu benennen ist besser, als
|
||||
Allgemeinheit vorzutäuschen, die beim nächsten Werkzeug (HedgeDoc, CryptPad —
|
||||
andere Export-Pfade) doch einen Typ-Diskriminator bräuchte.
|
||||
|
||||
**Das Textfeld ist schreibgeschützt.** Ohne das verschwände getippter Text beim
|
||||
nächsten Abruf — Datenverlust, und zwar überraschend, weil nichts darauf
|
||||
hindeutet. Der Schutz ist zugleich die ehrliche Aussage: Werkbaum kann
|
||||
gleichzeitige Änderungen nicht zusammenführen, das Pad kann es. Deshalb
|
||||
erscheint in der Editor-Titelzeile ein Knopf, der das Pad im neuen Tab öffnet
|
||||
(nur bei solchen Dokumenten sichtbar).
|
||||
|
||||
**Stabilitäts-Verzögerung statt rohem Polling.** Beim Abrufen sieht man die
|
||||
anderen mitten im Tippen; eine halb geschriebene Zeile ist eine kaputte Zeile,
|
||||
das Diagramm zuckt und die Warnungen flackern. Übernommen wird ein neuer Text
|
||||
deshalb erst, wenn **zwei** Abrufe hintereinander denselben liefern. Das kostet
|
||||
im Mittel einen Takt Verzögerung und macht die Ansicht ruhig. Bei `?sourceUrl=`
|
||||
gibt es das Problem nicht (man lädt bewusst neu) — es ist also spezifisch für
|
||||
diesen Parameter, wie das Polling selbst.
|
||||
|
||||
Getaktet wird mit 2,5 s und **nicht**, solange der Tab im Hintergrund liegt: Der
|
||||
Export rendert bei jedem Abruf das ganze Pad, und die Gegenseite ist fremde
|
||||
Infrastruktur. Ein Fehlschlag beim Abrufen bleibt **stumm** und lässt den
|
||||
letzten Stand stehen (nur der erste Ladeversuch warnt) — sonst flutete ein
|
||||
Netzaussetzer den Warnbereich.
|
||||
|
||||
**Verhältnis zu D20 („keine externen Requests").** Unverändert wie bei D23: Die
|
||||
App lädt von sich aus nichts; der Request entsteht nur, weil der Nutzer eine
|
||||
URL angibt, und geht nur an genau diesen Host (`credentials:'omit'`, nur
|
||||
`http`/`https`). Neu und ausdrücklich zu benennen ist die andere Richtung:
|
||||
**Der Plantext liegt jetzt auf fremder Infrastruktur**, und ein Pad ist für
|
||||
jeden lesbar, der die Adresse kennt. Das ist bei einem Projektplan etwas
|
||||
anderes als bei einer Schriftart. Es bleibt die Entscheidung dessen, der den
|
||||
Link baut — Werkbaum legt von sich aus kein Pad an.
|
||||
|
||||
**Verworfene und aufgeschobene Alternativen:**
|
||||
- **`?sourceUrl=` um Polling erweitern** — hätte bestehenden Links ungefragt
|
||||
wiederholte Requests verpasst und D23 seine klare Semantik gekostet.
|
||||
- **Das Pad als `<iframe>` einbetten** (aufgeschoben, nicht verworfen): Bringt
|
||||
Cursor, Namen, Farben und Chat mit. Kostet aber **beide** Richtungen von D25
|
||||
— in einem cross-origin-iframe gibt es keinen DOM-Zugriff, also kein
|
||||
Alt+Klick → Zeile und keine Cursor-Zeile → Knoten. Bei 75 Knoten ist das die
|
||||
Orientierung. Der Knopf „im Pad bearbeiten" ist die kleine Lösung desselben
|
||||
Bedürfnisses; das Einbetten bleibt möglich, weil die Pad-URL vorliegt.
|
||||
- **Echter Etherpad-Client** (socket.io + Easysync-Changesets): Rettet D25 und
|
||||
erlaubt Schreiben aus Werkbaum heraus, kostet aber zwei **Laufzeit**-
|
||||
Abhängigkeiten und damit „eine self-contained Datei ohne
|
||||
Laufzeit-Abhängigkeiten" (D11/D19/D20) — und die Diff-Hälfte müsste man
|
||||
selbst schreiben. Das ist der XL-Knoten aus dem Plan, nur mit fremdem
|
||||
Protokoll statt fremdem CRDT. Wenn, dann zusammen mit dem eigenen Backend
|
||||
(D13) und dann besser mit einem Text-CRDT, wie der Plan es vorsieht.
|
||||
- **Etherpads HTTP-API** (`/api/1/getText?apikey=…`) — der API-Schlüssel ist ein
|
||||
Administrationsschlüssel für **alle** Pads der Instanz und hat in einer
|
||||
Client-Anwendung nichts zu suchen. Der Export-Endpunkt braucht ihn nicht.
|
||||
|
||||
+33
-1
@@ -276,6 +276,37 @@ 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.
|
||||
- Geholt wird **regelmäßig** (Voreinstellung 2,5 s; nicht, solange der Tab im
|
||||
Hintergrund liegt). Übernommen wird eine Änderung erst, wenn zwei Abrufe
|
||||
hintereinander **denselben** neuen Text liefern — sonst sähe man die anderen
|
||||
mitten im Tippen, und halb geschriebene Zeilen ließen Diagramm und Warnungen
|
||||
flackern.
|
||||
- **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 einzelner
|
||||
Aussetzer beim Abrufen bleibt stumm und lässt den letzten Stand stehen.
|
||||
|
||||
Siehe D31.
|
||||
|
||||
### Legende im Editor-Panel
|
||||
Neben dem Textfeld steht eine aufklappbare **Legende** (Notation in Kurzform,
|
||||
abschließend eine Bedienungs-Zeile). Sie ist **scrollbar**, wenn ihr Inhalt
|
||||
@@ -287,7 +318,8 @@ 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 oder per `?sourceUrl=`), wird
|
||||
Bei Dokumenten, die von außen kommen (mitgeliefert, per `?sourceUrl=` oder
|
||||
`?etherpad=`), 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
|
||||
|
||||
@@ -61,7 +61,11 @@
|
||||
- [?] Accounts and permissions (L)
|
||||
| [?] Single user, one token (S)
|
||||
| [?] Log in with OIDC (L)
|
||||
- [?] Working together on one plan (XL)
|
||||
- [~] Working together on one plan (XL)
|
||||
- [x] Watch a shared Etherpad — it merges, we render (S)
|
||||
- [x] Load the pad's plain-text export (XS)
|
||||
- [x] Keep up while the others type (XS)
|
||||
+ [?] Embed the pad beside the diagram (M) %% costs the jump to the line
|
||||
- [ ] Git as the shared store (L)
|
||||
| [ ] A file in a repository, changed by pull request (S) %% works today, no code
|
||||
| [?] The backend commits every change (L)
|
||||
|
||||
Reference in New Issue
Block a user