docs+frontend: Gemeinsam arbeiten in Roadmap und Werkbaum-Plan
- docs/ROADMAP.md: neuer Abschnitt „Gemeinsam an einem Diagramm arbeiten" in drei Stufen — Lesen teilen (?sourceUrl=, vorhanden), asynchron über Git (heute schon möglich, weil der Plan Text ist: git diff/blame sind lesbar; Ausbau = Backend committet jede Änderung), live tippen (Transport ist der kleinere Teil, das Zusammenführen gleichzeitiger Änderungen der eigentliche; Text-CRDT passt wegen D14 ohne eigene Merge-Logik). Notiert auch, warum Git direkt aus dem Browser kein Live-Sync ist (Sync auf Zuruf, CORS-Proxy nötig, Konfliktmarker mitten in der Notation) und was vor einer Entscheidung offen ist (erste Laufzeit-Abhängigkeit, Commit-Granularität, Rechte). - docs/examples/example-werkbaum.werkbaum: passender Zweig „Working together on one plan" (94 Knoten, parst ohne Warnung). Dabei aufgefallen: Das mitgelieferte Dokument wurde nur EINMAL angelegt — die Ergänzung hätte niemanden erreicht, der die Seite schon geöffnet hatte. `werkbaum-seeded` hält deshalb statt '1' den Fingerabdruck (FNV-1a) der ausgelieferten Fassung; bei neuer Fassung wird der Text nachgezogen, aber nur wenn der Nutzer ihn nicht bearbeitet hat. Gelöscht bleibt gelöscht. `resetToDefaults()` setzt den Merker mit. Verifiziert im Browser, vier Fälle: alte unveränderte Fassung wird nachgezogen (98 Zeilen, neuer Zweig da); selbst geänderter Text bleibt unangetastet; gelöschtes Dokument kehrt nicht zurück; frischer Zustand zeigt die neue Fassung (89 sichtbare Knoten, keine Warnung). Vitest 37/37. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e160015fc7
commit
68abdc6625
+16
-6
@@ -649,9 +649,19 @@ Nutzerdaten. (Die erste Fassung hieß durch einen Tippfehler „Werkbank";
|
||||
ausgelieferte ist, damit eine eigene Umbenennung stehen bleibt.) Der Reset (D22) setzt jetzt **beide** mitgelieferten Dokumente auf
|
||||
ihren Auslieferungsstand zurück; eigene Dokumente bleiben weiterhin unangetastet.
|
||||
|
||||
**Offen:** Der Text wird nach dem Anlegen nicht mehr aktualisiert. Erscheint
|
||||
später eine neuere Fassung des Plans, sieht sie nur, wer das Dokument löscht und
|
||||
zurücksetzt oder den `?sourceUrl=`-Link öffnet. Ein Ausbau könnte in
|
||||
`werkbaum-seeded` statt '1' eine Versionsnummer ablegen und ein **unverändertes**
|
||||
Dokument nachziehen (bearbeitete nie) — dieselbe Adoptions-Regel wie beim
|
||||
Beispiel in D22.
|
||||
**Nachziehen bei neuer Fassung (nachgereicht).** Zuerst wurde der Text nur
|
||||
einmalig angelegt — eine spätere Ergänzung des Plans erreichte niemanden mehr.
|
||||
Das fiel sofort auf, als der Plan um den Abschnitt „gemeinsam arbeiten"
|
||||
(ROADMAP) wuchs. `werkbaum-seeded` hält deshalb nicht mehr '1', sondern den
|
||||
**Fingerabdruck** (FNV-1a) der zuletzt ausgelieferten Fassung. Beim Laden gilt:
|
||||
|
||||
- kein Merker → Dokument einmalig anlegen (auch für Bestandsnutzer);
|
||||
- Merker ≠ aktueller Fingerabdruck **und** der Text des Nutzers hat noch genau
|
||||
den gemerkten Fingerabdruck → Text nachziehen;
|
||||
- Text **verändert** → nie anfassen (dieselbe Adoptions-Regel wie beim Beispiel
|
||||
in D22: nur Unverändertes wird adoptiert);
|
||||
- Dokument gelöscht → bleibt gelöscht.
|
||||
|
||||
Der Altwert `'1'` aus der ersten Fassung sagt nichts über den Textstand; dort
|
||||
wird bewusst nichts überschrieben, nur der Merker ersetzt. Wer aus dieser kurzen
|
||||
Zwischenfassung kommt, holt den aktuellen Stand über den Reset.
|
||||
|
||||
@@ -44,6 +44,46 @@ in laufender Entwicklung, Integrationsfähigkeit erklärtes Ziel — noch zu
|
||||
jung als Plattform-Ziel. Companion-App so schneiden, dass ein späterer
|
||||
Umzug Taiga → Tenzu nur den API-Adapter betrifft.
|
||||
|
||||
## Gemeinsam an einem Diagramm arbeiten
|
||||
Ziel: mehrere Personen ändern denselben Plan. Drei Stufen, aufeinander
|
||||
aufbauend — die dritte lohnt nur, wenn wirklich gleichzeitig gearbeitet wird.
|
||||
|
||||
**1. Lesen teilen — vorhanden.** `?sourceUrl=` (D23) zeigt eine entfernte
|
||||
Textdatei an; die Quelle wird anderswo gepflegt. Einbahnstraße, aber ohne
|
||||
jeden Server.
|
||||
|
||||
**2. Asynchron ändern über Git — heute schon möglich, ohne eine Zeile Code.**
|
||||
Die `.werkbaum`-Datei (D24) liegt im Repository, wird per Pull Request geändert
|
||||
und per `?sourceUrl=` angezeigt. Weil der Plan **Text** ist (D14), sind
|
||||
`git diff` und `git blame` tatsächlich lesbar („wer hat *Payment provider* auf
|
||||
`[!]` gesetzt?"). Für einen Plan, der sich pro Woche und nicht pro Sekunde
|
||||
ändert, ist das oft die passendere Antwort — mit Review und Historie obendrauf.
|
||||
Ausbau: Das Backend (D13) legt jede Änderung als Commit ab und bekommt
|
||||
Historie, Wiederherstellung und Verzweigungen für Szenarien geschenkt.
|
||||
|
||||
*Git direkt aus dem Browser* ist technisch möglich (`isomorphic-git` spricht das
|
||||
HTTP-Smart-Protokoll), taugt aber nicht als Live-Sync: Es synchronisiert auf
|
||||
Zuruf statt fortlaufend, braucht wegen fehlender CORS-Header einen Proxy (SSH
|
||||
scheidet im Browser ohnehin aus), und ein Konflikt landet als
|
||||
`<<<<<<<`-Markierung mitten in der Notation — der Parser sähe kaputte Zeilen.
|
||||
|
||||
**3. Live gemeinsam tippen — offen.** Der **Transport** ist das kleinere
|
||||
Problem (WebSocket; SSE + POST oder WebRTC als Alternativen). Die eigentliche
|
||||
Frage ist, was passiert, wenn zwei Personen **dieselbe Zeile** ändern: Naives
|
||||
„jede Änderung sofort in beide Richtungen" führt zu Textsprüngen unter dem
|
||||
Cursor und verlorenen Zeichen. Erprobte Antworten sind **Operational
|
||||
Transformation** (Google Docs) und **CRDTs**. Für Werkbaum fällt die Wahl
|
||||
leicht, weil D14 das Format auf puren Text festgelegt hat: Ein Text-CRDT
|
||||
(`Y.Text` in Yjs, oder Loro) passt ohne eigene Merge-Logik darauf; Cursor und
|
||||
Anwesenheit fallen als Beigabe ab. **Eigene Merge-Algorithmen sind hier kein
|
||||
Betätigungsfeld** — das Problem ist gelöst.
|
||||
|
||||
Offene Punkte vor einer Entscheidung: Yjs wäre die **erste
|
||||
Laufzeit-Abhängigkeit** überhaupt (CLAUDE: nicht ohne Rückfrage); wie CRDT-
|
||||
Zustand und Git-Historie zusammenspielen (Commit-Granularität — nicht jeder
|
||||
Tastendruck ein Commit); und wer bei einem Backend eigentlich was darf
|
||||
(Rechte, siehe „Accounts" im Werkbaum-Beispielplan).
|
||||
|
||||
## Kleinere Ideen
|
||||
- Deterministische Pastellfarbe pro `@name` (Personen wiedererkennen).
|
||||
- Sichtbare Anmerkungen am Knoten (eigene Syntax, getrennt von `%%`).
|
||||
|
||||
@@ -58,6 +58,21 @@
|
||||
- [?] Accounts and permissions (L)
|
||||
| [?] Single user, one token (S)
|
||||
| [?] Log in with OIDC (L)
|
||||
- [?] Working together on one plan (XL)
|
||||
- [ ] 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)
|
||||
- [?] History and restore (M)
|
||||
- [?] Diff between two versions (S)
|
||||
- [?] Who changed this line (S)
|
||||
- [?] A branch per scenario (S)
|
||||
- [?] Live editing, several people at once (XL)
|
||||
- [ ] Transport over a websocket (S) %% the easy half
|
||||
- [!] Merging simultaneous edits (L) %% the actual work
|
||||
| [?] Text CRDT — the plan is plain text, so it fits (L)
|
||||
| [?] Operational transformation (XL)
|
||||
- [?] Cursors and who else is here (S)
|
||||
- [-] A merge algorithm of our own (XL) %% solved problem, do not reinvent
|
||||
- [?] Mermaid plugin (XL)
|
||||
- [!] A layout engine of its own (XL) %% measure, place, route — the real work
|
||||
- [ ] Measure node sizes (M)
|
||||
|
||||
+6
-3
@@ -110,9 +110,12 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
|
||||
`../../docs/examples/example-werkbaum.werkbaum?raw` — die Beispieldatei ist
|
||||
damit **Build-Eingabe**, Umbenennen/Verschieben bricht den Build (Zugriff
|
||||
außerhalb des Roots erlaubt `server.fs.allow:['..']`). `seedShippedDocs()` legt
|
||||
es **einmalig** an (auch für Bestandsnutzer) und merkt sich das in
|
||||
`werkbaum-seeded`; ohne den Merker käme ein gelöschtes Dokument bei jedem Laden
|
||||
zurück. `resetToDefaults()` setzt beide mitgelieferten Dokumente zurück.
|
||||
es **einmalig** an (auch für Bestandsnutzer); ohne den Merker `werkbaum-seeded`
|
||||
käme ein gelöschtes Dokument bei jedem Laden zurück. Der Merker hält den
|
||||
**Fingerabdruck** der ausgelieferten Fassung: Ändert sich die Datei, wird der
|
||||
Text nur nachgezogen, wenn der Nutzer ihn **nicht** bearbeitet hat. Wer die
|
||||
Beispieldatei ändert, ändert damit das mitgelieferte Dokument mit.
|
||||
`resetToDefaults()` setzt beide mitgelieferten Dokumente **und** den Merker.
|
||||
- Umbenennen ist **inline** (kein `window.prompt` — in manchen Browser-Kontexten
|
||||
unterdrückt): `renameDoc()` setzt `renamingId`, `renderDocMenu()` rendert dann
|
||||
ein `<input class="docrename">` (Enter = `commitRename`, Esc = `cancelRename`,
|
||||
|
||||
+29
-11
@@ -1340,18 +1340,33 @@ function persistDocs(){
|
||||
if(d) localStorage.setItem(LS_SRC, d.text); /* Spiegel für Fallback/Migration */
|
||||
}catch(_){}
|
||||
}
|
||||
/* „Werkbank" (D27) genau EINMAL anlegen — auch für Bestandsnutzer, die schon
|
||||
eine Dokumentenliste haben. Der Merker `LS_SEEDED` sorgt dafür, dass ein
|
||||
bewusst gelöschtes Dokument nicht bei jedem Laden zurückkehrt; ein späterer
|
||||
Ausbau kann dort eine Versionsnummer statt '1' ablegen. */
|
||||
function seedShippedDocs(){
|
||||
let seeded = null;
|
||||
try{ seeded = localStorage.getItem(LS_SEEDED); }catch(_){}
|
||||
if(seeded) return;
|
||||
if(!docs.some(d => d.id === WERKBAUM_ID)){
|
||||
docs.push({ id: WERKBAUM_ID, name: WERKBAUM_NAME, text: WERKBAUM_DOC });
|
||||
/* Kurzer, stabiler Fingerabdruck (FNV-1a) — dient nur dem Vergleich „ist das
|
||||
noch der ausgelieferte Text?"; keine kryptografische Anforderung. */
|
||||
function fingerprint(s){
|
||||
let h = 0x811c9dc5;
|
||||
for(let i = 0; i < s.length; i++){ h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); }
|
||||
return (h >>> 0).toString(36);
|
||||
}
|
||||
try{ localStorage.setItem(LS_SEEDED, '1'); }catch(_){}
|
||||
|
||||
/* Mitgeliefertes Dokument „Werkbaum" (D27) anlegen bzw. nachziehen. In
|
||||
`LS_SEEDED` steht der Fingerabdruck der zuletzt ausgelieferten Fassung:
|
||||
- kein Merker -> Dokument einmalig anlegen (auch für Bestandsnutzer);
|
||||
- neue Fassung -> Text nur ersetzen, wenn der Nutzer ihn NICHT geändert hat
|
||||
(sein Fingerabdruck also noch dem gemerkten entspricht);
|
||||
- gelöscht -> bleibt gelöscht (der Merker verhindert die Wiederkehr).
|
||||
Der Altwert '1' aus der ersten Fassung sagt nichts über den Textstand, dort
|
||||
wird bewusst nichts angefasst — nur der Merker wird ersetzt. */
|
||||
function seedShippedDocs(){
|
||||
const fp = fingerprint(WERKBAUM_DOC);
|
||||
let seen = null;
|
||||
try{ seen = localStorage.getItem(LS_SEEDED); }catch(_){}
|
||||
const doc = docs.find(d => d.id === WERKBAUM_ID);
|
||||
if(!seen){
|
||||
if(!doc) docs.push({ id: WERKBAUM_ID, name: WERKBAUM_NAME, text: WERKBAUM_DOC });
|
||||
} else if(seen !== '1' && seen !== fp && doc && fingerprint(doc.text) === seen){
|
||||
doc.text = WERKBAUM_DOC;
|
||||
}
|
||||
try{ localStorage.setItem(LS_SEEDED, fp); }catch(_){}
|
||||
}
|
||||
|
||||
/* Aus dem localStorage laden; bei fehlender Dokumentenliste den bestehenden
|
||||
@@ -2065,6 +2080,9 @@ function resetToDefaults(){
|
||||
};
|
||||
reseed(EXAMPLE_ID, EXAMPLE_NAME, INITIAL, true);
|
||||
reseed(WERKBAUM_ID, WERKBAUM_NAME, WERKBAUM_DOC, false);
|
||||
/* Merker auf den jetzt ausgelieferten Stand setzen (D27) — sonst hielte ein
|
||||
Altwert die spätere Nachzieh-Logik davon ab, den Text je zu aktualisieren. */
|
||||
try{ localStorage.setItem(LS_SEEDED, fingerprint(WERKBAUM_DOC)); }catch(_){}
|
||||
activeId = EXAMPLE_ID;
|
||||
persistDocs();
|
||||
logUpdate('🔄 Mitgelieferte Dokumente und Einstellungen zurückgesetzt');
|
||||
|
||||
Reference in New Issue
Block a user