Fix Docs undo/redo cursor restore and split reliability#164
Conversation
Each mutation's doc.update() now saves cursor position to presence with addToHistory: true. This stores the pre-mutation cursor as the reverse presence, so Yorkie automatically restores it on undo. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Use getChangeID().getActorID() and getPresenceForTest() instead of accessing internal Yorkie fields via any cast. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Instead of jumping to document start (blocks[0], offset:0), the editor now reads cursor position from Yorkie presence after undo/redo. The presence is automatically restored by Yorkie's history mechanism. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
setDocument now records the undo stack depth as a floor. canUndo() returns false when the stack reaches this floor, preventing users from undoing the initial document load which causes CRDT node conflicts on subsequent redo. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The separate doc.update() for writing pre-cursor to presence caused CRDT node conflicts during redo. Yorkie's reverse presence mechanism captures the pre-mutation presence automatically from the previousPresence snapshot, so the extra call is unnecessary. In production, updateCursorPos already keeps presence current. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
splitLevel=2 caused CRDTTreePos errors during redo because the styleByPath referenced nodes invalidated by the undo cycle. Replace with explicit two-step approach matching the mergeBlock pattern: 1. Delete text after split point from original block 2. Insert new block with the removed content This also fixes the cross-boundary editByPath bug that required the mergeByPath workaround for split-created blocks. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
📝 WalkthroughWalkthroughThis PR implements cursor position persistence into Yorkie presence linked to undo/redo history. The block-splitting mechanism transitions from native CRDT to manual two-step operations. An undo-floor prevents reverting past the initial document load, and helpers manage cursor state consumption and restoration across mutations. Changes
Sequence DiagramsequenceDiagram
actor User
participant Editor as Editor
participant DocStore as YorkieDocStore
participant Presence as Yorkie<br/>Presence
participant History as Yorkie<br/>History Stack
User->>Editor: Perform edit action
Editor->>DocStore: insertText/deleteText/splitBlock
DocStore->>DocStore: setPendingCursorPos (position before mutation)
DocStore->>History: doc.update(...) mutation
History->>DocStore: Mutation applied
DocStore->>Presence: activeCursorPos = consumedPendingCursorPos<br/>(with addToHistory: true)
Presence->>History: Cursor position recorded in undo stack
User->>Editor: Trigger Undo
Editor->>DocStore: canUndo() check (not past undoFloor)
DocStore->>History: Undo operation
History->>Presence: Restore prior activeCursorPos state
DocStore->>Editor: getPresenceCursorPos() returns restored position
Editor->>Editor: Restore caret to block with clamped offset
Editor->>User: Display cursor at restored position
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Verification: verify:selfResult: ✅ PASS in 124.0s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Summary
splitLevel=2with manual two-step split for reliable undo/redoDetails
Cursor restore: Each mutation's
doc.update()now saves cursorposition to Yorkie presence with
addToHistory: true. On undo/redo,Yorkie automatically restores the pre/post-mutation cursor. The editor
reads the restored presence and calls
cursor.moveTo().Undo floor:
setDocumentrecords the undo stack depth as a floor.canUndo()returns false at this floor, preventing users from undoingthe initial document load which causes CRDT node conflicts on redo.
Manual split:
splitLevel=2causedCRDTTreePoserrors duringredo because
styleByPathreferenced nodes invalidated by the undocycle. Replaced with explicit two-step approach (delete after-text +
insert new block), matching the
mergeBlockpattern. This also fixescross-boundary
editByPathon split-created blocks.Test plan
pnpm verify:fastpasses (all tests green)🤖 Generated with Claude Code
Summary by CodeRabbit