@@ -16,6 +16,8 @@ import 'package:flutter/foundation.dart' show immutable;
1616import 'package:flutter_riverpod/flutter_riverpod.dart' ;
1717
1818import 'package:pyble/editor/editor.dart' ;
19+ import 'package:pyble/editor/board_target_guard.dart' ;
20+ import 'package:pyble/editor/draft_recovery.dart' ;
1921
2022import 'blocks_companion.dart' ;
2123import 'blocks_examples.dart' ;
@@ -1038,22 +1040,57 @@ class BlocksDocumentController extends Notifier<BlocksDocument> {
10381040 /// Copies a freshly acknowledged revision into an independent editor document.
10391041 ///
10401042 /// Callers must explicitly resolve a dirty-buffer conflict.
1041- Future <void > openInEditor ({bool replaceDirty = false }) async {
1043+ Future <void > openInEditor ({
1044+ bool replaceDirty = false ,
1045+ int ? expectedEditorEpoch,
1046+ }) async {
1047+ final int editorEpoch =
1048+ expectedEditorEpoch ?? ref.read (editorDocumentEpochProvider);
1049+ if (ref.read (editorDocumentEpochProvider) != editorEpoch) {
1050+ throw BlocksEditorConflict (ref.read (editorDocumentProvider).name);
1051+ }
10421052 _beginAction ();
10431053 try {
10441054 final GeneratedProgram program = await _freshProgramForAction ();
1055+ final int ? capturedHostId = _activeHostId;
1056+ if (ref.read (editorDocumentEpochProvider) != editorEpoch) {
1057+ throw BlocksEditorConflict (ref.read (editorDocumentProvider).name);
1058+ }
10451059 final EditorDocument editor = ref.read (editorDocumentProvider);
10461060 final String generatedName = state.targetPath.substring (
10471061 state.targetPath.lastIndexOf ('/' ) + 1 ,
10481062 );
10491063 final bool sameGeneratedDocument =
10501064 editor.name == generatedName && editor.content == program.source;
1051- if (editor.dirty && ! sameGeneratedDocument && ! replaceDirty) {
1065+ final recoveryEnabled =
1066+ ref.read (draftRecoveryProvider).status !=
1067+ DraftRecoveryStatus .disabled;
1068+ if (! recoveryEnabled &&
1069+ editor.dirty &&
1070+ ! sameGeneratedDocument &&
1071+ ! replaceDirty) {
10521072 throw BlocksEditorConflict (editor.name);
10531073 }
1054- ref
1055- .read (editorDocumentProvider.notifier)
1056- .openGenerated (name: generatedName, content: program.source);
1074+ if (recoveryEnabled &&
1075+ ! await confirmEditorReplacement (ref, expectedEpoch: editorEpoch)) {
1076+ throw const BoardActionCancelled ();
1077+ }
1078+ final adopted = await ref
1079+ .read (draftRecoveryProvider.notifier)
1080+ .replaceDocument (
1081+ EditorDocument (
1082+ name: generatedName,
1083+ content: program.source,
1084+ dirty: true ,
1085+ ),
1086+ expectedEpoch: editorEpoch,
1087+ isStillValid: () =>
1088+ capturedHostId == _activeHostId &&
1089+ state.program? .revision == program.revision &&
1090+ state.retainedWorkspaceRevision == program.revision &&
1091+ state.workspaceError == null ,
1092+ );
1093+ if (! adopted) throw const BoardActionCancelled ();
10571094 } finally {
10581095 _endAction ();
10591096 }
0 commit comments