Consider an original dispatch note shared by two editors. One author changes the loading bay while the other adds a handling instruction. Both start from the same saved revision. When the authority accepts one change first, the other client needs that accepted history before resending its remaining local changes.
Load the document and version as one snapshot
For an application that already has a compatible schema, EditorState setup, and server transport:
import {
collab, getVersion, sendableSteps, receiveTransaction
} from 'prosemirror-collab';
import { Step } from 'prosemirror-transform';
const synchronization = collab({
version: snapshot.version,
clientID: connectionClientId
});
Include synchronization once in that state’s plugins and construct the document from snapshot.doc with the same schema. The version must describe that exact document. Starting with today's document and version zero creates a false history baseline.
connectionClientId identifies this editing client in the protocol. Keep it stable while its outstanding steps are pending, and give concurrent clients distinct identities. It is not an authenticated user identity or a permission check. The application must authorize document access and writes at the server.
Serialize the current unconfirmed steps
The following function reads a transport payload without changing editor state:
function pendingStepPayload(state) {
const pending = sendableSteps(state);
if (!pending) return null;
return {
version: pending.version,
clientID: pending.clientID,
steps: pending.steps.map(step => step.toJSON())
};
}
Send it through your document-specific authenticated transport. The authority should accept a batch only against its matching current version, validate and apply its steps, and append them to the ordered history. Define that contract on the server; installing the plugin supplies no endpoint, storage service, or transport.
Serialize outstanding send attempts, or explicitly track which batch is in flight. Reading sendableSteps() repeatedly can return steps already being transmitted. A successful HTTP response alone does not remove them from the plugin's pending set. The collab API distinguishes unconfirmed local steps from the version received from the authority.
Receive history in order, including your own accepted changes
Suppose the transport supplies a batch whose version is the starting version before its steps. Each entry in clientIDs corresponds to the step at the same index:
function applyAcceptedBatch(view, batch) {
if (batch.version !== getVersion(view.state)) {
throw new Error('Fetch the missing ordered history first.');
}
if (batch.steps.length !== batch.clientIDs.length) {
throw new Error('Every accepted step needs its client ID.');
}
const steps = batch.steps.map(json =>
Step.fromJSON(view.state.schema, json)
);
view.dispatch(
receiveTransaction(view.state, steps, batch.clientIDs)
);
}
The application must validate the incoming message shape and route it to the intended document before calling this integration function. Its version field is an example transport convention, not a response format the package imposes on your service.
receiveTransaction() recognizes the client's acknowledged steps, advances the received version, and rebases remaining unconfirmed changes over incoming remote steps. Do not manually erase a pending batch or replay your own acknowledged steps as new local insertions. The module source shows that acknowledgment and rebasing behavior.
If the authority rejects a stale send, retrieve the accepted history since the client's received version, apply it in order, and read a fresh sendableSteps() payload. Its steps may have changed through rebasing. Sending the original JSON repeatedly retains the original positional assumptions.
Test the conflict and the acknowledgment separately
Start two development clients from one fictional dispatch note at version zero. Make a different local insertion in each before delivering either. Let the authority accept A's step first, deliver that accepted step to both clients with A's ID, and inspect their states.
A should have no pending step for its accepted insertion. B should retain its own insertion, rebased over A's accepted history. Send B's current payload at the new version, have the authority accept it, and deliver that accepted batch to both. Their final documents and received versions should match, and neither should have pending steps.
Also disconnect before an acknowledgment arrives. Reconnection must reconcile accepted history and in-flight state without duplicating an instruction. Persist the authority's document and history/version consistently so reopening starts from a valid snapshot.
This central ordered-step protocol differs from the Yjs document binding in the Tiptap collaboration guide. Choose the synchronization architecture deliberately. Finish when both authors see each instruction once and a fresh client can load the authority's saved result at the corresponding version.