feat: Adresszeile folgt dem Dokument, Live-Sitzung ebenso (D80)

Wer bei offenem `?live=` umschaltet, behielt die alte Adresse — optisch
falsch, und ein Neuladen holte das falsche Dokument zurück. Der Parameter
gehört jetzt zum aktiven Dokument, für `?live=` wie für `?sourceUrl=`;
`?etherpad=` wird nur noch weggeräumt. Fremde Parameter (`?server=`)
bleiben wörtlich stehen, damit die URL lesbar bleibt.

Dabei gefunden: Die Live-Sitzung lief weiter, während ein anderes Dokument
vorn stand — `setLiveText()` schrieb fremde Änderungen in dessen Text. Die
Sitzung gehört jetzt dem sichtbaren Dokument: Umschalten beendet sie,
Umschalten auf ein Server-Dokument nimmt sie auf. Was noch im Debounce
steckt, geht vorher raus, und `pushLive()` hält seine Sitzung fest statt
anzunehmen, dass sich über ein `await` hinweg nichts ändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-26 21:19:29 +02:00
co-authored by Claude Opus 5
parent 627f6feca8
commit fdca934cf0
8 changed files with 313 additions and 20 deletions
+3
View File
@@ -19,6 +19,9 @@ reverse.
## 2026-08-26
- The address bar follows the document you switch to: `?live=` and `?sourceUrl=` name what is in front of you, so a reload brings back the same plan
- Switching to a server document in the picker now really opens it live — before it only showed its last state
- Fix: a foreign change to a server document could land in the text of a local document you had switched to
- Changes in a shared document now reach the others after 0.6 s instead of 1.5 s — the wait before sending was almost the whole delay
- The Etherpad integration is gone: collaboration now runs through a Werkbaum backend, and an old `?etherpad=` link says so instead of doing nothing
+82
View File
@@ -6819,3 +6819,85 @@ die 600 ms sind gesetzt, und was daneben liegt (Server 39 ms, Rundlauf 130 ms,
PATCH → sichtbar 46 ms) ist einzeln gemessen. Dieselbe Lehre wie D25
(synthetische `TouchEvent`s), D17-Nachtrag 4 (Bildschirmtastatur) und D53
(synthetisches Strg+Z): Was die Umgebung stellt, stellt der Emulator nicht.
## D80 — Die Adresszeile beschreibt das aktive Dokument, und die Live-Sitzung folgt ihm
Gemeldet: Wer bei offenem `?live=…` auf ein anderes Dokument umschaltet, behält
die alte Adresse — „das sieht optisch falsch aus, und beim Neuladen würde wohl
auch das Dokument aus `live=` wieder geladen". Beides stimmt, und beim
Nachsehen kam ein dritter, schwererer Befund dazu.
**Die Adresse ist kein Andenken an den Aufruf, sondern der Stand.** Sie ist der
Link, den man weitergibt, und das, was ein Neuladen wiederherstellt. Zeigt sie
auf etwas anderes als der Bildschirm, ist eines von beiden gelogen — und beim
Neuladen entscheidet die Adresse. Die Regel lautet deshalb: **Der Parameter
gehört zum aktiven Dokument.** Umschalten auf ein lokales Dokument räumt ihn
weg, Umschalten auf ein anderes Server-Dokument tauscht ihn aus.
**Sie gilt für beide Eingänge, nicht nur für `?live=`.** `?sourceUrl=` (D23)
hatte dasselbe Problem, und eine Regel, die nur für einen der beiden gilt, ist
keine. Beide Eingänge sind ohnehin schon die **Identität** des Dokuments
(`live:<url>`, `url:<href>`) — der Parameter lässt sich also aus der id
zurückrechnen, statt nebenher geführt zu werden. `?etherpad=` ist ausgebaut
(D78) und wird nur noch weggeräumt.
**Für `?sourceUrl=` ist das keine neue Gefahr**, obwohl der Parameter beim
Zurückschalten wiederkommt und ein Neuladen den Text dann erneut holt (D23:
„lokale Änderungen daran überleben ein Neuladen nicht"). Bisher stand er
**immer** da, unabhängig davon, was vorn war — es wird also nicht mehr
überschrieben als vorher, sondern weniger.
**Fremde Parameter bleiben wörtlich stehen — auch ihre Schreibweise.** Der
naheliegende Weg über `URLSearchParams` schriebe jedes `:` und `/` als
`%3A`/`%2F` und machte damit gerade die URL unleserlich, um die es hier geht;
`?server=` (D76-Nachtrag 8) fiele bei einem Neubau der Adresse ganz weg.
Maskiert wird nur, was den Query-String sonst zerrisse (`&`, `#`). Die
entscheidbare Hälfte steht als reine Funktion in `docurl.js`
(Hausregel D54-Nachtrag 3), die `history.replaceState`-Seite in app.js.
**Der dritte Befund: Die Live-Sitzung lief weiter, während ein anderes Dokument
vorn stand.** `switchDoc()` hat bisher nur `activeId` gewechselt; `liveState`
blieb, der Feed lief, und `setLiveText()` schreibt in `src.value` **und** in
`activeDoc().text` — eine fremde Änderung am Server-Dokument landete also im
Text des Dokuments, das man gerade ansieht. Nachgemessen war die Lücke echt:
Die Meldung des Nutzers ist die Tür dazu.
**Also gehört die Sitzung dem sichtbaren Dokument.** Umschalten beendet sie;
Umschalten auf ein Server-Dokument nimmt sie auf (`startLive()`, aus
`loadLive()` herausgelöst — derselbe Weg, nur mit der URL aus dem Dokument
statt aus dem Parameter). Der Nebengewinn ist der eigentliche: Ein
Server-Dokument, das man im Wähler auswählt, ist danach wirklich live. Vorher
zeigte es stumm seinen letzten Stand — die Adresse hätte also nicht nur
optisch, sondern der Sache nach gelogen, wenn man sie einfach mitgeführt hätte.
**Verdrahtet an genau einer Stelle:** `loadActiveIntoEditor()` — jeder Weg zu
einem anderen aktiven Dokument führt dort durch (Umschalten, Anlegen, Löschen,
Datei öffnen, Server-Dokument laden). Während des Starts ruht die Regel
(`bootDone`): `loadRemoteSource()` und `loadLive()` lesen ihre Parameter erst,
nachdem das zuletzt aktive Dokument wiederhergestellt ist — ein vorschnelles
Aufräumen nähme ihnen die Vorlage.
**Was noch im Debounce steckt, wird beim Umschalten losgeschickt.** Sonst
verlöre ein Wechsel innerhalb von 600 ms nach dem letzten Tastendruck genau
diese Änderung an den Server. Gesendet wird, **bevor** `activeId` wechselt —
`pushLive()` liest `src.value` synchron, danach zeigt das Feld schon den
anderen Text.
**Und `pushLive()` hält jetzt seine Sitzung fest, nicht nur deren Felder.**
Wer während des Sendens umschaltet, beendet sie; die Fortsetzung nach dem
`await` dürfte danach weder schreiben noch in ein `null` greifen (das
`finally` hätte es getan). Dieselbe Sorte Annahme, die D76-Nachtrag 9 schon
einmal an dieser Funktion korrigiert hat: dass sich über ein `await` hinweg
nichts ändert.
**Nachgemessen** im Browser gegen ein lokales Backend, mit zwei
Server-Dokumenten: Umschalten auf ein lokales Dokument räumt `?live=` weg,
Umschalten auf das andere Server-Dokument tauscht die URL aus, `?sourceUrl=`
verhält sich symmetrisch und bleibt unmaskiert lesbar. Eine fremde Änderung
erreicht das per Wähler geöffnete Server-Dokument ohne Neuladen (die Sitzung
läuft also wirklich); dieselbe Änderung, während ein lokales Dokument vorn
steht, lässt dessen Text unangetastet (vorher hätte sie ihn überschrieben);
beim Zurückschalten steht der Server-Stand da. Eine Zeile, im selben Zug
getippt und umgeschaltet, kommt beim Server an. 514 Tests, davon 13 neue in
`tests/docurl.test.js`; Gegenproben: fremde Parameter mitwerfen → genau die
zwei danach benannten Zusicherungen fallen, `encodeURIComponent` statt der
sparsamen Maskierung → genau die sieben, die die Lesbarkeit festhalten.
+10
View File
@@ -766,6 +766,9 @@ Seite aufgelöst; zugelassen sind nur `http`/`https`). Der Text wird als
Tooltip); derselbe Link aktualisiert dieses Dokument, statt ein neues anzulegen.
Ist der Parameter gesetzt, wird bei **jedem** Laden neu geholt — die URL ist die
Quelle der Wahrheit, lokale Änderungen daran überleben ein Neuladen nicht.
Die **Adresszeile folgt dem aktiven Dokument** (§9, `?live=`): Beim Umschalten
auf ein anderes verschwindet der Parameter, beim Zurückschalten steht er wieder
da.
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.
@@ -796,6 +799,13 @@ anderen, ohne neu zu laden.
jede Version in der Historie des Servers.
- Der Abruf läuft **nur im sichtbaren Tab**; im Hintergrund ruht er und holt
beim Zurückkommen den Rückstand.
- **Die Adresszeile beschreibt das Dokument, das vorn ist.** Umschalten auf ein
lokales Dokument räumt `?live=` weg, Umschalten auf ein anderes
Server-Dokument trägt dessen Adresse ein — ein Neuladen bringt also zurück,
was man vor sich hatte. Dasselbe gilt für `?sourceUrl=` (oben). Und die
gemeinsame Sitzung gehört dem sichtbaren Dokument: Wer ein Server-Dokument im
Wähler auswählt, arbeitet darin wieder gemeinsam; wer es verlässt, hört auf,
mitzuschreiben. Siehe D80.
Siehe D76 (Protokoll und Begründung) und
`backend/docs/live-editing-proposal.md`.