diff --git a/js/app/packages/core/component/LexicalMarkdown/collaboration/undo.ts b/js/app/packages/core/component/LexicalMarkdown/collaboration/undo.ts index 246a85ccbf..e782eec306 100644 --- a/js/app/packages/core/component/LexicalMarkdown/collaboration/undo.ts +++ b/js/app/packages/core/component/LexicalMarkdown/collaboration/undo.ts @@ -219,10 +219,25 @@ export function registerLoroHistory( excludeOriginPrefixes: ['history-'], }); + const tryGroupStart = () => { + try { + undoManager.groupStart(); + } catch (e) { + console.error( + 'Something went wrong starting a undo group (probably group start called multiple times). Ignoring..', + e + ); + } + }; + // Track if we're currently in a group let isGroupActive = false; let prevChangeTime = Date.now(); let prevChangeType = OTHER; + // Don't record history until the editor has been initialized and CLEAR_HISTORY_COMMAND + // has been dispatched. Without this guard, setEditorState during initialization triggers + // groupStart() on a Loro doc that may have no committed ops, causing a WASM panic. + let isReadyForHistory = false; const getMergeAction = ( prevState: null | EditorState, @@ -309,6 +324,10 @@ export function registerLoroHistory( dirtyLeaves: Set; tags: Set; }): void => { + if (!isReadyForHistory) { + return; + } + const mergeAction = getMergeAction( prevEditorState, editorState, @@ -328,12 +347,12 @@ export function registerLoroHistory( isGroupActive = false; } // Start a new group for the next changes - undoManager.groupStart(); + tryGroupStart(); isGroupActive = true; } else if (mergeAction === HISTORY_MERGE) { // If we're merging and no group is active, start one if (!isGroupActive) { - undoManager.groupStart(); + tryGroupStart(); isGroupActive = true; } } @@ -393,6 +412,7 @@ export function registerLoroHistory( isGroupActive = false; } undoManager.clear(); + isReadyForHistory = true; editor.dispatchCommand(CAN_UNDO_COMMAND, false); editor.dispatchCommand(CAN_REDO_COMMAND, false); return false; @@ -410,6 +430,7 @@ export function registerLoroHistory( isGroupActive = false; } undoManager.clear(); + isReadyForHistory = true; editor.dispatchCommand(CAN_UNDO_COMMAND, false); editor.dispatchCommand(CAN_REDO_COMMAND, false); return true;