feat: collaborative undo/redo for simpleton editors#7
Open
bxff wants to merge 1 commit intobraid-org:masterfrom
Open
feat: collaborative undo/redo for simpleton editors#7bxff wants to merge 1 commit intobraid-org:masterfrom
bxff wants to merge 1 commit intobraid-org:masterfrom
Conversation
Adds a client-side undo/redo system for simpleton editors, modeled after Loro's OT-based approach (loro-dev/loro#361). The undo stack stores inverse patches and transforms them through remote edits so positions stay valid across concurrent editing. New files: client/undo-sync.js - undo_manager with inverse, transform, record/undo/redo test/undo-tests.js - 15 unit + integration tests Modified files: client/simpleton-sync.js - expose client_state getter for text_before capture client/editor.html - wire undo_manager, intercept Ctrl+Z/Ctrl+Shift+Z client/markdown-editor.html - wire undo_manager, intercept Ctrl+Z/Ctrl+Shift+Z test/test.js - register undo test suite Key design decisions: - Patches in undo groups use offset-accumulation (same as apply_patches_and_update_selection) - capture_timeout (500ms default) groups rapid keystrokes into single undo steps - transform_group implements Loro's d_i := transform(d_i, r); r := transform(r, d_i) - Overlapping remote deletes collapse undo entries gracefully (acknowledged limitation)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ctrl+Z is unreliable in collaborative sessions. Remote patches arrive through
apply_patches_and_update_selection, which setstextarea.valueprogrammatically. Browsers clear their undo stack for everything typed before a.valueset, so every incoming remote edit wipes the undo history. In practice, you can only undo the last few keystrokes since the most recent remote patch, and in an active session with frequent edits that means undo barely works.This adds a client-side undo manager that maintains its own stack. On each local edit, it records an inverse patch (the deleted text and the range needed to undo the insertion). On Ctrl+Z it pops the stack and applies the inverse. Rapid keystrokes within 500ms get grouped into a single undo step.
The tricky part is making this work with concurrent editing. If another user inserts text before your undo range, the stored positions are now wrong. To handle this, every time remote patches arrive through
on_patches, the undo manager transforms its entire stack through the remote edit. Each stored range gets shifted by the remote insertion/deletion using the sametransform_poslogic thatcursor-sync.jsalready uses for cursor positions. This is the same approach described in loro-dev/loro#361.Changes
client/undo-sync.js: new file. The undo manager (~217 lines).record()captures inverse patches.transform()walks both stacks adjusting ranges through remote edits.undo()/redo()apply and swap between stacks. One implementation detail:apply_patches_and_update_selectionmutates patch range arrays in-place, so inverse computation has to run before the apply call.client/simpleton-sync.js: expose aclient_stategetter so the undo manager can read the pre-edit text for inverse computation.client/editor.html,client/markdown-editor.html: wire the undo manager into the simpleton/cursor flow. Intercept Ctrl+Z, Ctrl+Shift+Z, Ctrl+Y.test/undo-tests.js: new file. 15 tests covering inverse correctness, redo round-trips, transform through remote inserts and deletes, capture timeout grouping, overlapping remote deletes, and a two-peer HTTP integration test.test/test.js: register the undo test suite and exposeundo_manageras a global for the test runner.Limitations
localStorageor recovering state from the server are natural follow-ups.