From ec6fd452b7d7016a97fa5e76aed51d1a20a73b20 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Wed, 26 Aug 2026 17:50:13 +0200 Subject: [PATCH] docs: der mitgelieferte Plan sagt, was wirklich gebaut wurde MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live-Editing stand dort als Websocket plus CRDT — gegangen sind wir einen anderen Weg: Zeilen-Diffs ueber gewoehnliches HTTP, der Server verschiebt selbst, und nur bei echter Ueberschneidung entscheidet ein Mensch. Der Websocket-Knoten ist damit verworfen, nicht offen; die neue Antwort steht als dritte Alternative neben CRDT und OT. Dazu der Backend-Teil: Geruest, Dokumente, Historie, Teilen per Link und der Passwortschutz der Liste sind fertig. `[x]`, nicht `[^]` — befoerdert wird beim Deploy (D30). 209 Knoten, alle mit ID und Beschreibung, 0 Warnungen. Co-Authored-By: Claude Opus 5 --- docs/examples/werkbaum.werkbaum | 74 ++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/docs/examples/werkbaum.werkbaum b/docs/examples/werkbaum.werkbaum index adc5bfb..a03b47a 100644 --- a/docs/examples/werkbaum.werkbaum +++ b/docs/examples/werkbaum.werkbaum @@ -110,14 +110,16 @@ - [-] #bld.ghpages: Deploy through a gh-pages branch (S) %% GitHub Actions instead - [-] #bld.gfonts: Fonts from Google (XS) %% self-hosted, no third-party request - [ ] #be: Backend (XL) - - [ ] #be.scaffold: Kotlin and Spring Boot scaffold (M) - - [ ] #be.scaffold.gradle: Gradle Kotlin DSL, JDK 21 (S) + - [/] #be.scaffold: Kotlin and Spring Boot scaffold (M) + - [x] #be.scaffold.gradle: Gradle Kotlin DSL, JDK 21 (S) - [ ] #be.scaffold.ci: Health endpoint and CI (S) - - [ ] #be.docs: Documents on the server (L) :#be.scaffold - - [ ] #be.docs.model: Text plus metadata, no structure format (M) - - [ ] #be.docs.rest: REST endpoints (S) - - [ ] #be.docs.db: Persistence (S) - - [ ] #be.docs.share: Share by link (S) + - [x] #be.docs: Documents on the server (L) :#be.scaffold + - [x] #be.docs.model: Text plus metadata, no structure format (M) + - [x] #be.docs.rest: REST endpoints (S) + - [x] #be.docs.db: Persistence (S) + - [x] #be.docs.history: History in two layers, and restore (S) + - [x] #be.docs.share: Share by link (S) + - [x] #be.docs.list: Master password for the document list (S) - [?] #be.auth: Accounts and permissions (L) :#be.docs | [?] #be.auth.token: Single user, one token (S) | [?] #be.auth.oidc: Log in with OIDC (L) @@ -153,12 +155,17 @@ - [?] #col.git.diff: Diff between two versions (S) - [?] #col.git.blame: Who changed this line (S) - [?] #col.git.branch: A branch per scenario (S) - - [?] #col.live: Live editing, several people at once (XL) - - [ ] #col.live.ws: Transport over a websocket (S) :#be.scaffold %% the easy half - - [!] #col.live.merge: Merging simultaneous edits (L) %% the actual work + - [/] #col.live: Live editing, several people at once (XL) :#be.docs + - [x] #col.live.http: Transport over plain HTTP (M) :#be.docs %% no websocket needed + - [x] #col.live.patch: Submit a change as a line diff (S) + - [x] #col.live.feed: Long polling holds the other direction open (S) + - [x] #col.live.merge: Merging simultaneous edits (L) %% the actual work + | [x] #col.live.diff: Line diffs against a base version; the server rebases (L) | [?] #col.live.crdt: Text CRDT — the plan is plain text, so it fits (L) | [?] #col.live.ot: Operational transformation (XL) + - [x] #col.live.ask: On a real overlap, ask whose version wins (S) - [?] #col.live.cursors: Cursors and who else is here (S) + - [-] #col.live.ws: Transport over a websocket (S) %% plain HTTP turned out to be enough - [-] #col.live.own: A merge algorithm of our own (XL) %% solved problem, do not reinvent - [?] #mmd: Mermaid plugin (XL) - [!] #mmd.layout: A layout engine of its own (XL) %% measure, place, route — the real work @@ -757,9 +764,19 @@ Somewhere to keep them. Whether that also keeps their history is decided in the git branch of this plan, not here. +#be.docs.history + Every change is kept as a snapshot. Two layers: milestones are the history + people read, sync versions carry the diffs of live editing and are compacted + after a while. + #be.docs.share - A link that shows a plan to someone who has no account. The read-only half - of collaboration, and by far the cheaper one. + A link that shows a plan to someone who has no account. It is no longer the + read-only half: with live editing the same link is the way in. + +#be.docs.list + Listing all documents would make every document's address findable, so that + one endpoint asks for a master password. Without a configured one the list + stays shut rather than open. #be.auth As soon as documents live on a server, the question is who may do what. Open @@ -899,13 +916,37 @@ Everybody typing at once, cursors and all. The largest single piece of work in this plan, and the least necessary one. -#col.live.ws - The transport is the easy half: a socket, and a backend to terminate it. +#col.live.http + Plain request and response, no websocket: a change goes up as a line diff, + and one held-open request brings the others' changes down. It is testable + with the same tools as everything else, needs no special treatment in + proxies or service workers, and at ten onlookers the efficiency difference + is beside the point. + +#col.live.patch + Every change is a diff against a known version, with a checksum of that + version. A repeated submission is recognised and not applied twice — over a + mobile connection the lost answer is the normal case, not the exception. + +#col.live.feed + The server holds the request open until something happens, then answers at + once. Costs one open request per onlooker and runs only in the visible tab. #col.live.merge What happens when two people change the same line. The actual work — and a solved problem, which is exactly why it should not be solved again here. +#col.live.diff + The answer we took: changes are line diffs against a base version. Where two + changes do not touch the same lines the server shifts them itself, so nobody + has to retry; where they do, a human decides. No runtime dependency, and the + same model on both sides. + +#col.live.ask + On a real overlap a bar asks whose version should win. Whoever loses loses + nothing: the discarded state stays in the local snapshots, and every version + stays in the server's history. + #col.live.crdt Because the plan is plain text, a text CRDT fits it without any merge logic of our own, and cursors come along as a by-product. The price would be the @@ -919,6 +960,11 @@ Seeing where the others are. A by-product of the merge layer rather than a feature in its own right. +#col.live.ws + A socket and a backend to terminate it — long the obvious answer, and not + needed after all: request and response carry a diff just as well, and + everything around them stays simpler. + #col.live.own Rejected: writing a merge algorithm is a research project with a known answer.