Incorrect Undo Behavior in Collaborative Editing with Cross-Version Scenarios

Title: Incorrect Undo Behavior in Collaborative Editing with Cross-Version Scenarios

Hello everyone,

I am working on a collaborative editing project and have encountered an issue where the undo operation does not work correctly in cross-version scenarios. In my implementation, I use a rebase strategy to handle local uncommitted steps. However, I found that in certain cases, when the local uncommitted steps span across multiple versions, the undo operation exhibits incorrect behavior.

From my understanding, the official collaborative editing plugin does not seem to have a mechanism to handle cross-version scenarios. Therefore, I would like to seek advice on how to correctly handle undo operations in cross-version scenarios within collaborative editing.

What does “cross-version” mean here?

Cross-version refers to the scenario where the base version and the step version are not consecutive when step data is generated. This occurs when users A and B submit edits simultaneously:

  1. Users A and B both start editing the document based on version 1.

  2. User A submits the edits, generating version 2. Simultaneously, user B submits edits, and the server generates version 3. The results are then returned to users A and B respectively.

  3. User A receives version 2 and calls the receiveCommitTransaction function. To test the correctness of complex scenarios, the local edits will also be reverted even when submitted by the user. Version 2 is detected as a local submission, so the corresponding invert steps are applied, and the invertMaps are recorded. Then, the version 2 edits are applied, and since it’s detected as a local change, setMirror is called to modify the mapping. User A receives version 3 and calls the receiveCommitTransaction function. They find that the base version of version 3 is 1, so they get the steps of version 2 for mapping, and then apply the mapped version 3 edits.

  4. Users receive version 3, and the WebSocket (ws) receives version 2 (continues waiting). When ws receives version 3, it calls the receiveCommitTransaction function. Version 3 is detected as a local submission, and the corresponding invert steps are applied. Then, the version 2 edits are applied. When applying version 3, it is found that the base version of version 3 is 1, so the steps of version 2 are mapped, and the mapped version 3 edits are applied. Since it’s detected as a local change, setMirror is called to modify the mapping.

  5. When considering uncommitted local versions, the process follows the official code and is completed within the rebaseSteps function. Since localSteps contains the added AddSelectStep, it is excluded during the local rebase process.

  6. Finally, the length of the rebased steps is recorded using setMeta(‘rebased’, remoteLen + len), which covers both instances of the rebase process.

I don’t think this is even possible using ProseMirror’s step mapping system, unless you follow the strategy of prosemirror-collab, and have the server reject user B’s changes until they rebase them to match the server’s version.

In any case, I don’t intend to debug a custom collab implementation.

Thanks marijn for reply. Since we also need to implement collaboration for other editors, the plan is for the server to only handle recording and controlling versions, while the client takes care of the rebase process. The current code works well for content collaboration, but there seems to be some oversight in managing the history, causing confusion when performing undo actions.

I found the cause of the problem. It’s because I merged the steps before sending the data, but I encountered a new issue. Since I set historyPreserveItems to true, the history of Chinese input cannot be automatically merged. Now, when I input Chinese and then undo, it will first revert to pinyin and then delete. Can I control the merging of their history?