-
Notifications
You must be signed in to change notification settings - Fork 290
Description
Package Version
super_editor: 0.3.0-dev.43
User Info
Individual
To Reproduce
Steps to reproduce the behavior:
- Open a document with text content in the editor
- Type some text and make edits (e.g., delete selections, insert text)
- Click the undo button just one time
- Observe the error when undo operations fail
Minimal Reproduction Code
The bug occurs when undo operations fail due to invalid document positions. Here's a simplified version of our implementation:
Minimal, Runnable Code Sample
// In SuperDocumentController
void undo() {
if (!_editor.isHistoryEnabled) return;
if (!_editor.history.isNotEmpty) return;
_performAsyncSafeAction(() {
_editor.undo();
});
}
void _performAsyncSafeAction(VoidCallback action) {
// Unfocus to avoid IME race conditions
final focusNode = _focusNode;
if (focusNode != null && focusNode.hasFocus) {
focusNode.unfocus();
}
// Defer undo to next frame
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
if (!_editor.isHistoryEnabled) return;
action();
_updateUndoRedoState();
} catch (e, stackTrace) {
// Error caught here, but document state may be inconsistent
print('Undo failed: $e');
} finally {
_isPerformingUndoRedo = false;
if (focusNode != null && !focusNode.hasFocus) {
requestFocus();
}
}
});
}
Actual behavior
When undo operations fail, the following sequence occurs:
-
First error (caught):
Null check operator used on a null valueinDeleteSelectionCommand.executeatmulti_node_editing.dart:1180:63- The undo operation fails partway through execution
- Document state may be partially modified but selection state is inconsistent
-
Second error (uncaught, causes crash):
No such position in document: [DocumentPosition]in_IosDocumentTouchInteractorState._ensureSelectionExtentIsVisible- Occurs in the
didChangeMetricscallback after undo completes - The iOS touch interactor tries to ensure selection visibility, but the selection position is invalid
- Error details:
node: "7b7888e5-d748-460a-8d4c-ff84b25c3d96", position: (TextPosition(offset: 4, affinity: TextAffinity.downstream))
- Occurs in the
-
Cascading errors: Multiple "Null check operator used on a null value" errors follow, indicating the document state is corrupted
Error Stack Traces:
[E] Error: Null check operator used on a null value
#0 DeleteSelectionCommand.execute (package:super_editor/src/default_editor/multi_node_editing.dart:1180:63)
#1 _DocumentEditorCommandExecutor.executeCommand (package:super_editor/src/core/editor.dart:705:15)
#2 Editor._executeCommand (package:super_editor/src/core/editor.dart:316:22)
#3 Editor.undo (package:super_editor/src/core/editor.dart:420:32)Exception: No such position in document: [DocumentPosition] - node:
"7b7888e5-d748-460a-8d4c-ff84b25c3d96", position: (TextPosition(offset: 4, affinity:
TextAffinity.downstream))
#0 InspectDocumentAffinity.getAffinityBetween (package:super_editor/src/core/document_selection.dart:330:7)
#1 InspectDocumentAffinity.getAffinityForSelection (package:super_editor/src/core/document_selection.dart:300:12)
#2 _IosDocumentTouchInteractorState._ensureSelectionExtentIsVisible (package:super_editor/src/default_editor/document_gestures_touch_ios.dart:464:52)
#3 _IosDocumentTouchInteractorState.didChangeMetrics. (package:super_editor/src/default_editor/document_gestures_touch_ios.dart:448:7)
Expected behavior
- Undo operations should complete successfully or fail gracefully without corrupting document state
- If an undo operation fails, the document and selection state should remain consistent
- The iOS touch interactor should handle invalid selection positions gracefully without crashing
- Subsequent operations should not fail due to corrupted state from a failed undo
Platform
- iOS (tested on iPhone SE, iOS 26)
Flutter version
Flutter 3.35.7
Additional context
Root cause analysis:
- State inconsistency: When DeleteSelectionCommand.execute fails during undo, the document may be partially modified but the selection state is not properly restored, leaving an invalid selection position.
- Async callback conflict: After undo completes (even if it failed), UI updates and layout changes trigger didChangeMetrics callbacks. The iOS touch interactor tries to ensure selection visibility, but the selection position is invalid, causing a crash.
- Incomplete error handling: While we catch exceptions in the undo operation itself, we don't handle the side effects (invalid selection state) that occur after undo fails. The iOS touch interactor's callbacks are not protected by our try-catch.
- Race condition: The issue is exacerbated by the async nature of undo operations (we defer to next frame to avoid IME race conditions), which means layout callbacks can fire while the document is in an inconsistent state.
Suggested fixes:
- Validate selection state after undo operations and reset to a safe position if invalid
- Add defensive checks in _IosDocumentTouchInteractorState._ensureSelectionExtentIsVisible to handle invalid positions gracefully
- Ensure undo operations are atomic - either fully succeed or fully rollback without partial state changes
- Add selection state validation before attempting to ensure visibility in layout callbacks
This issue appears to be related to how super_editor handles document state consistency when undo operations fail, particularly on iOS where touch interactors have additional callbacks that can trigger during state transitions.