Package Version
0.3.0-dev.50
User Info
Individual for now.
To Reproduce
- Create a SuperEditor with a pre-populated document and isHistoryEnabled: true
- Give the editor focus so that a DocumentSelection is established
- Execute a compound Editor.execute that inserts two new nodes around the selection (e.g. InsertNodeBeforeNodeRequest + InsertNodeAfterNodeRequest) — this causes SuperEditor to remount DocumentSelectionOpenAndCloseImePolicy with the existing selection still active
- See the exception in the Flutter error console
Minimal Reproduction Code
Minimal, Runnable Code Sample
import 'package:flutter/material.dart';
import 'package:super_editor/super_editor.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: EditorPage());
}
}
class EditorPage extends StatefulWidget {
const EditorPage({super.key});
@override
State<EditorPage> createState() => _EditorPageState();
}
class _EditorPageState extends State<EditorPage> {
late final MutableDocument _document;
late final MutableDocumentComposer _composer;
late final Editor _editor;
@override
void initState() {
super.initState();
_document = MutableDocument(nodes: [
ParagraphNode(id: Editor.createNodeId(), text: AttributedText('First paragraph')),
ParagraphNode(id: Editor.createNodeId(), text: AttributedText('Second paragraph')),
]);
_composer = MutableDocumentComposer();
_editor = createDefaultDocumentEditor(
document: _document,
composer: _composer,
isHistoryEnabled: true,
);
}
void _wrapInMarkers() {
final selection = _composer.selection;
if (selection == null) return;
final startNodeId = selection.base.nodeId;
// Insert a marker node before and after the selected node.
// This causes SuperEditor to remount DocumentSelectionOpenAndCloseImePolicy
// while the selection is still non-null — triggering the crash.
_editor.execute([
InsertNodeBeforeNodeRequest(
existingNodeId: startNodeId,
newNode: ParagraphNode(
id: Editor.createNodeId(),
text: AttributedText('--- START ---'),
),
),
InsertNodeAfterNodeRequest(
existingNodeId: startNodeId,
newNode: ParagraphNode(
id: Editor.createNodeId(),
text: AttributedText('--- END ---'),
),
),
]);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('IME Bug Reproduction'),
actions: [
TextButton(
onPressed: _wrapInMarkers,
child: const Text('Wrap', style: TextStyle(color: Colors.white)),
),
],
),
body: SuperEditor(
editor: _editor,
focusNode: FocusNode()..requestFocus(),
),
);
}
}
Actual behavior
The following exception is thrown immediately after Editor.execute completes:
setState() or markNeedsBuild() called during build.
_DocumentSelectionOpenAndCloseImePolicyState.initState
document_ime_interaction_policies.dart:291
The exception originates because initState calls _onSelectionChange() and _onConnectionChange() synchronously. _onConnectionChange → _clearSelectionIfDesired → widget.editor.execute([ClearSelectionRequest()]) → resumeNotifications() → notifyListeners() → setState() on a widget that is still in the build phase.
Expected behavior
No exception is thrown. The editor remounts cleanly with the existing selection intact, and IME state is reconciled on the next frame.
Platform
Reproduced on Windows desktop. Expected to affect all platforms (Android, iOS, macOS, Linux, Web) since this is a widget lifecycle issue, not platform-specific IME behavior.
Flutter version
Flutter 3.41.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 582a0e7c55 (5 weeks ago) • 2026-02-12 17:12:32 -0800
Engine • hash cc8e596aa65130a0678cc59613ed1c5125184db4 (revision 3452d735bd)
(1 months ago) • 2026-02-09 22:03:17.000Z
Tools • Dart 3.11.0 • DevTools 2.54.1
Screenshots
N/A — the bug manifests as a console exception, not a visual artifact.
Additional context
The fix is a one-line deferral in initState. didUpdateWidget already applies the same deferral for _onConnectionChange via SchedulerBinding.runAsSoonAsPossible (lines ~311–319). The initState path simply needs the same treatment:
// In _DocumentSelectionOpenAndCloseImePolicyState.initState
// BEFORE (buggy):
if (widget.selection.value != null) {
_onSelectionChange();
_onConnectionChange();
}
// AFTER (fixed):
if (widget.selection.value != null) {
onNextFrame((_) {
_onSelectionChange();
_onConnectionChange();
});
}
onNextFrame is already defined in lib/src/infrastructure/flutter/flutter_scheduler.dart within this package. No new dependencies are needed.
Package Version
0.3.0-dev.50
User Info
Individual for now.
To Reproduce
Minimal Reproduction Code
Minimal, Runnable Code Sample
Actual behavior
The following exception is thrown immediately after Editor.execute completes:
setState() or markNeedsBuild() called during build.
_DocumentSelectionOpenAndCloseImePolicyState.initState
document_ime_interaction_policies.dart:291
The exception originates because initState calls _onSelectionChange() and _onConnectionChange() synchronously. _onConnectionChange → _clearSelectionIfDesired → widget.editor.execute([ClearSelectionRequest()]) → resumeNotifications() → notifyListeners() → setState() on a widget that is still in the build phase.
Expected behavior
No exception is thrown. The editor remounts cleanly with the existing selection intact, and IME state is reconciled on the next frame.
Platform
Reproduced on Windows desktop. Expected to affect all platforms (Android, iOS, macOS, Linux, Web) since this is a widget lifecycle issue, not platform-specific IME behavior.
Flutter version
Flutter 3.41.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 582a0e7c55 (5 weeks ago) • 2026-02-12 17:12:32 -0800
Engine • hash cc8e596aa65130a0678cc59613ed1c5125184db4 (revision 3452d735bd)
(1 months ago) • 2026-02-09 22:03:17.000Z
Tools • Dart 3.11.0 • DevTools 2.54.1
Screenshots
N/A — the bug manifests as a console exception, not a visual artifact.
Additional context
The fix is a one-line deferral in initState. didUpdateWidget already applies the same deferral for _onConnectionChange via SchedulerBinding.runAsSoonAsPossible (lines ~311–319). The initState path simply needs the same treatment:
onNextFrame is already defined in lib/src/infrastructure/flutter/flutter_scheduler.dart within this package. No new dependencies are needed.