2.8 KiB
collabd - editor-agnostic collaborative editing daemon
daemon + thin adapters architecture. daemon handles yjs crdt, adapters just hook buffer events and apply remote changes.
status
working:
- daemon with room-based sessions
- multi-peer sync (tested with 2+ clients)
- vim9 adapter with live buffer sync
- proper crdt diffing (not delete-all-insert-all)
- integration tests for concurrent edits
not yet:
- cursor/selection sync (awareness protocol stubbed but unused)
- other editor adapters (helix, kakoune, zed)
- reconnection handling
stack
- bun runtime
- yjs for crdt
- websocket transport
- vim9script adapter (with bun bridge since vim cant do websocket)
running
just dev # daemon on :4040
COLLABD_DB=path.db just dev # custom db path (default: collabd.db)
bun test # unit tests
just check # biome lint
vim adapter usage
requires Vim 9.0+ (uses vim9script)
:source adapters/vim/collab.vim
:CollabJoin roomname
:CollabLeave
:CollabStatus
the vim plugin spawns adapters/vim/bridge.ts which handles yjs and speaks json lines to vim via channels.
protocol
daemon speaks json over websocket at /ws
client -> server: { type: "join", room: "name" } { type: "leave" } { type: "update", data: [...] } // yjs update bytes
server -> client: { type: "sync", data: [...] } // full yjs state on join { type: "update", data: [...] } // remote changes { type: "peers", count: N }
key files
- src/index.ts - websocket server, room routing
- src/session.ts - yjs doc per room, peer management
- src/protocol.ts - message types
- src/db.ts - sqlite persistence, save/load crdt updates
- adapters/vim/bridge.ts - bun process vim spawns
- adapters/vim/collab.vim - vim9script plugin
adding new editor adapters
each adapter needs:
- hook buffer change events
- send changes to daemon as yjs updates (or use a bridge like vim does)
- receive remote updates and apply to buffer
- optionally show peer cursors
see NOTES.txt for cell-grid vs text-crdt mode discussion. see docs/ for full research and architecture breakdown.
security
current state (research prototype):
- room name validation (alphanumeric, 1-64 chars)
- message type validation via protocol decoder
- websocket.send error handling
known gaps (not production ready):
- no authentication (anyone can join any room by name)
- no authorization (all peers see all edits)
- no rate limiting on messages or room creation
- no message size limits
- room names guessable via brute force
- no encryption (deploy behind wss, not ws)
- no audit logging
before production:
- auth layer (jwt tokens or unix socket for local-only)
- per-room authorization
- rate limiting (msgs/sec, rooms/minute)
- message size caps
- tls via reverse proxy
- access logging