Skip to content

Fix Docs undo/redo cursor restore and split reliability#164

Merged
hackerwins merged 6 commits into
mainfrom
fix/docs-undo-redo-cursor-and-split
Apr 27, 2026
Merged

Fix Docs undo/redo cursor restore and split reliability#164
hackerwins merged 6 commits into
mainfrom
fix/docs-undo-redo-cursor-and-split

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Restore cursor position after undo/redo instead of jumping to document start
  • Prevent undo past initial document load (avoids CRDT redo conflicts)
  • Replace splitLevel=2 with manual two-step split for reliable undo/redo

Details

Cursor restore: Each mutation's doc.update() now saves cursor
position 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: setDocument records the undo stack depth as a floor.
canUndo() returns false at this floor, preventing users from undoing
the initial document load which causes CRDT node conflicts on redo.

Manual split: splitLevel=2 caused CRDTTreePos errors during
redo because styleByPath referenced nodes invalidated by the undo
cycle. Replaced with explicit two-step approach (delete after-text +
insert new block), matching the mergeBlock pattern. This also fixes
cross-boundary editByPath on split-created blocks.

Test plan

  • pnpm verify:fast passes (all tests green)
  • Manual: type text, Enter, type more → Ctrl+Z repeatedly → Ctrl+Y repeatedly
  • Manual: cursor stays at edit position after undo/redo (no jump to top)
  • Manual: verify split/merge in table cells still works
  • Manual: verify styled text split preserves styles

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Undo/redo operations now restore the cursor position to its location before the action.
    • Cursor position is now preserved in document history for improved editing continuity.

hackerwins and others added 6 commits April 26, 2026 21:42
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]>
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Cursor Presence & History Integration
packages/docs/src/view/editor.ts, packages/frontend/src/app/docs/yorkie-doc-store.ts
Undo/redo now restores cursor position from Yorkie presence via getPresenceCursorPos() with block validation and offset clamping; snapshot callbacks record cursor position via setCursorForHistory(). YorkieDocStore consumes pendingCursorPos during mutations, persists active cursor position to presence with addToHistory: true, and enforces an undoFloor to prevent undo past document load. Block splitting changed from native CRDT (splitLevel=2) to manual two-step split with inline reconstruction and new block insertion.
Block Merge Validation Tests
packages/frontend/tests/app/docs/merge-repro.test.ts
New test suite validates block-merge behavior across splitBlock, mergeByPath, and editByPath operations using deterministic paragraph construction and tree structure inspection.
Undo/Redo History Coverage
packages/frontend/tests/app/docs/yorkie-doc-store.test.ts
Extended test coverage for splitBlock undo→redo round-trips, cursor restoration via presence during undo/redo, and deleteText/splitBlock history scenarios; adjusted canUndo/canRedo test to reflect undo-floor semantics; added regression test (skipped) documenting redo duplication issue.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A cursor hops through time so deep,
With presence whispers, memories to keep,
Split blocks unite, undo's soft ground,
History anchored, no floor to bound,
Each edit saves where the carrot's found! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main objectives of the PR: fixing undo/redo cursor restoration and improving split reliability in the document editor.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/docs-undo-redo-cursor-and-split

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 124.0s

Lane Status Duration
sheets:build ✅ pass 12.6s
docs:build ✅ pass 8.1s
verify:fast ✅ pass 61.5s
frontend:build ✅ pass 17.2s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.9s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 17.6s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 29 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/docs/src/view/editor.ts 0.00% 29 Missing ⚠️

📢 Thoughts on this report? Let us know!

@hackerwins
hackerwins merged commit f564d04 into main Apr 27, 2026
4 checks passed
@hackerwins
hackerwins deleted the fix/docs-undo-redo-cursor-and-split branch April 27, 2026 14:02
@hackerwins hackerwins mentioned this pull request May 1, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant