Remove outdated claims (CRDT, DocRooms, presence, .md files on disk) and document the actual architecture, API, permissions and known flaws. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
3.5 KiB
Echtzeit: WebSocket und SSE
Der Server verteilt Änderungen als JSON-Ereignisse an verbundene Clients. Die Ereignisse entstehen nach einer REST-Änderung; es gibt keinen Schreibkanal über die Verbindung. Implementierung: hub.go, Client: api.ts.
Keine CRDTs, keine Sperren, keine Cursor-Presence. Frühere Fassungen dieser Doku beschrieben DocRooms,
clientId-Echo-Schutz und Yjs; das existiert nicht (mehr).
Transporte
| Transport | Endpunkt | Hinweise |
|---|---|---|
| WebSocket | GET /api/v1/ws (auch /ws/lists/{id}, /ws/spaces/{id}) |
Ping alle 30 s, Lesefrist 60 s, Nachrichten bis 4 MB |
| SSE | GET /api/v1/events |
Kommentar-Ping alle 15 s, Header X-Accel-Buffering: no |
Der Client versucht zuerst WebSocket. Gelingt der Aufbau nicht innerhalb von 2,5 s oder bricht die Verbindung unsauber ab, wechselt er auf SSE und bleibt dabei (Firmen-Proxys). Dort liefert der Server SSE_CONNECTED mit einer sseId; Räume werden über POST /api/v1/events/rooms (sseId, room, action: join/leave) abonniert.
Authentifizierung: Token als ?token=<JWT|API-Key> oder Authorization-Header. Ohne gültiges Token wird die Verbindung als anonymous akzeptiert (FLAW-01, FLAW-02). Das Token wird nur beim Verbinden geprüft.
Nachrichtenformat
{ "event": "TASK_UPDATED", "listId": "", "payload": { … }, "timestamp": "2026-10-27T12:00:00Z" }
Client → Server versteht der Server nur: {"event":"JOIN_ROOM","room":"<id>"} und {"event":"LEAVE_ROOM","room":"<id>"}. Räume heißen doc:<docId> für Notizen oder <listId> für geteilte Listen.
Zustellung
| Kanal | Wer erhält es |
|---|---|
Benutzer-Kanal (BroadcastToUser) |
alle Verbindungen eines Benutzers |
Raum (BroadcastRoom, Broadcast) |
Verbindungen im Raum |
Alle (BroadcastToAll) |
jede Verbindung, auch unangemeldete |
Ereignisse
| Ereignis | Auslöser | Kanal |
|---|---|---|
TASK_CREATED |
POST /inbox |
Benutzer |
TASK_UPDATED |
PATCH /thoughts/{id} (Triage: Benutzer) |
alle |
TASK_DELETED |
DELETE /thoughts/{id} |
alle |
TASK_REORDERED |
Aufgaben umsortieren | alle |
TASK_LIST_UPDATED / TASK_LIST_DELETED |
Liste ändern, (un)teilen, löschen | alle |
DOC_CREATED, DOC_UPDATED, DOC_DELETED |
Notiz anlegen/ändern/löschen, Freigaben | berechtigte Benutzer |
DOC_BLOCKS_PATCHED |
PATCH /documents/{id}/blocks (docId, updatedBy, upserted, deletedIds, blockOrder) |
Raum doc:<id> und berechtigte Benutzer |
WORKSPACE_FOLDERS_UPDATED |
Ordner/Mounts ändern | Benutzer |
LIST_CREATED, LIST_UPDATED, LIST_DELETED, LIST_SHARED, LIST_UNSHARED, LIST_CLEARED |
Listenverwaltung | Benutzer bzw. Raum der Liste |
ITEM_CREATED, ITEM_UPDATED, ITEM_TOGGLED, ITEM_DELETED, ITEM_REORDERED |
Listeneinträge | Raum der Liste |
USER_PRESENCE |
Verbindung auf-/abgebaut (onlineCount) |
Raum |
SSE_CONNECTED |
Nur SSE, direkt nach Verbindung | Verbindung |
Verhalten im Frontend
- Die globale Verbindung (ein Singleton) leitet jede Nachricht als Browser-Ereignis
mindsift:wsweiter; Ansichten (TasksView,WorkspaceView,SharedListsView) hören darauf und laden bzw. patchen ihren Zustand. - Nach einem Abbruch verbindet sich der Client mit Verzögerung neu.
- Weil Notizen nur vom Eigentümer geschrieben werden, dient
DOC_BLOCKS_PATCHEDvor allem dazu, Lesenden und weiteren Geräten des Eigentümers Änderungen zu zeigen. Es gibt keine Konfliktauflösung (letzter Schreiber gewinnt).