Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Add pause()/resume() to UndoManager (or expose checkout without stack clear) #1050

Copy link
Copy link

Description

@thomasalvord
Issue body actions

Problem

When building a document history slider (time-travel UI), I need to call doc.checkout(frontiers) to preview past states, then doc.checkoutToLatest() to return. Both calls trigger EventTriggerKind::Checkout, which unconditionally clears the UndoManager's undo and redo stacks (undo.rs line 667-673).

This means users lose their entire Ctrl+Z/Y history just by previewing document history — even though no edits were made. The history slider is read-only; the user is only viewing past states, not modifying anything.

Current workaround

Destroying and re-creating the UndoManager after checkoutToLatest(), which also loses all undo history. The alternative — cloning the entire LoroDoc just for read-only preview — is heavyweight and complex to plumb through the rendering layer.

Use case

  1. User types text (undo stack has entries)
  2. User opens a history slider to preview past document states (checkout to various frontiers)
  3. User closes the slider (checkoutToLatest)
  4. User presses Ctrl+Z — expected: undo works; actual: undo stack is empty

The document returns to the exact same state after the round-trip, so the undo stack entries are still valid — they just get cleared by the checkout event handler.

Proposed fix: pause() / resume() on UndoManager

This follows the exact pattern already used by processing_undo in undo.rs (line 610/828), which causes the event handler to skip processing during undo operations. A paused flag would work identically.

Loro's own undo_internal (loro.rs lines 1148-1169) already performs checkouts that don't clear the stacks — it calls _checkout_without_emitting and skips emit_events(). The proposed fix achieves the same result from the consumer side.

Changes (~35 lines across 2 files)

crates/loro-internal/src/undo.rs

1. Add paused: bool field to UndoManagerInner (after processing_undo):

paused: false,

2. Guard the event subscription handler (inside UndoManager::new(), line 605):

let undo_sub = doc.subscribe_root(Arc::new(move |event| {
    {
        let lock = inner_clone.lock();
        if lock.borrow().paused {
            return;
        }
    }
    match event.event_meta.by {
        // ... existing arms unchanged ...
    }
}));

3. Add public methods (after clear_undo()):

pub fn pause(&self) {
    self.inner.lock().borrow_mut().paused = true;
}

pub fn resume(&self) {
    self.inner.lock().borrow_mut().paused = false;
}

pub fn is_paused(&self) -> bool {
    self.inner.lock().borrow().paused
}

crates/loro-wasm/src/lib.rs

Add WASM bindings (after clearUndo()):

pub fn pause(&self) {
    self.undo.lock().pause();
}

pub fn resume(&self) {
    self.undo.lock().resume();
}

pub fn isPaused(&self) -> bool {
    self.undo.lock().is_paused()
}

Usage

undoManager.pause();
doc.checkout(someFrontiers);
// ... read-only preview ...
doc.checkoutToLatest();
undoManager.resume();
// undo/redo stacks are intact

Why this is safe

  • Mirrors the existing processing_undo flag pattern already in undo.rs
  • Loro's own undo_internal already does checkouts that skip the stack clear (via _checkout_without_emitting + no emit_events())
  • paused defaults to false, so no behavioral change for existing users
  • While paused, Import events (remote changes) are also skipped — but for a read-only history preview round-trip this is fine since the doc returns to the same state

Environment

  • loro-crdt: 1.12.1
  • Platform: WASM/JS (browser)
Reactions are currently unavailable

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    Morty Proxy This is a proxified and sanitized view of the page, visit original site.