Collaborative Editing¶
The :collab module provides the client-side infrastructure for real-time collaborative editing using operational transformation (OT).
How It Works¶
Collaborative editing in Kodemirror follows a central-authority model:
- A server (the authority) maintains the canonical document and a version counter.
- Each client tracks its own version and sends local changes to the server.
- The server applies changes, increments the version, and broadcasts updates to all clients.
- Clients receive remote updates and rebase any unconfirmed local changes on top of them.
The collab() extension handles the rebasing automatically. Your job is to wire up the transport layer (WebSocket, HTTP polling, etc.).
Setup¶
Add the :collab dependency and install the extension:
val session = rememberEditorSession(
doc = initialDoc,
extensions = basicSetup + collab(CollabConfig(startVersion = serverVersion))
)
CollabConfig¶
| Property | Type | Default | Description |
|---|---|---|---|
startVersion | Int | 0 | Document version when the editor was initialized |
clientID | String? | null | Unique client identifier (auto-generated if null) |
sharedEffects | ((Transaction) -> List<StateEffect<*>>)? | null | Extract effects to share with other clients |
Sending Local Changes¶
After each transaction, call sendableUpdates() to get pending changes:
val updates = sendableUpdates(session.state)
if (updates.isNotEmpty()) {
val version = getSyncedVersion(session.state)
sendToServer(version, updates)
}
Each SendableUpdate contains the ChangeSet, the clientID, any shared effects, and the originating Transaction.
Receiving Remote Changes¶
When the server broadcasts updates from other clients:
fun onServerUpdate(updates: List<Update>) {
val spec = receiveUpdates(session.state, updates)
session.dispatch(spec)
}
receiveUpdates() returns a TransactionSpec that, when dispatched, applies the remote changes and rebases any unconfirmed local changes on top of them.
Querying State¶
val version = getSyncedVersion(session.state) // Current synced version
val id = getClientID(session.state) // This client's ID
Server-Side Rebasing¶
If your server rejects updates because they were based on a stale version, use rebaseUpdates() on the server to transform them:
This is useful when clients submit updates concurrently and the server needs to reconcile the order.
Sharing Custom Effects¶
Some state effects (like cursor positions, annotations, or markers) should be shared across clients. Use sharedEffects in the config:
val markEffect = StateEffect.define<MarkSpec>()
val session = rememberEditorSession(
doc = doc,
extensions = basicSetup + collab(CollabConfig(
startVersion = version,
sharedEffects = { tr ->
tr.effects.filter { it.isType(markEffect) }
}
))
)
Shared effects are included in SendableUpdate.effects and delivered to other clients via Update.effects.
Undo/Redo Integration¶
The collab() extension works with the history() extension. Each client's undo history is independent — undoing reverts only that client's own changes, even when interleaved with remote edits.
val session = rememberEditorSession(
doc = doc,
extensions = basicSetup + collab(config) + history()
)
// Undo only reverts this client's changes
Example: Polling Architecture¶
A minimal polling-based collaboration client:
class CollabClient(
private val session: EditorSession,
private val serverUrl: String
) {
suspend fun pushPull() {
// 1. Push local changes
val updates = sendableUpdates(session.state)
if (updates.isNotEmpty()) {
pushToServer(serverUrl, updates, getSyncedVersion(session.state))
}
// 2. Pull remote changes
val version = getSyncedVersion(session.state)
val remote = pullFromServer(serverUrl, version)
if (remote.isNotEmpty()) {
session.dispatch(receiveUpdates(session.state, remote))
}
}
}
For production use, consider a WebSocket-based transport for lower latency.
API Reference¶
| Function | Description |
|---|---|
collab(config) | Install collaborative editing extension |
sendableUpdates(state) | Get pending local updates to send |
receiveUpdates(state, updates) | Build a TransactionSpec from remote updates |
getSyncedVersion(state) | Get the current synced version number |
getClientID(state) | Get this client's ID |
rebaseUpdates(updates, over) | Rebase out-of-date updates (server-side) |
Based on the CodeMirror Collaborative Editing approach.