diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs index a2b9562..79f5b50 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs @@ -129,6 +129,7 @@ internal sealed class FirstLimitedApplyAutomationCoordinator } private FirstApplyAutomationConfiguration? config; private Document? document; + private readonly List automationOpenedDocuments = []; private readonly List completed = []; private int step; private int? before, after1, after2, afterUndo; @@ -159,9 +160,10 @@ internal sealed class FirstLimitedApplyAutomationCoordinator var project = PlantProject.LoadProject(projectXml, false, string.Empty, string.Empty); PlantApplication.SetCurrentProject(project, true); transitions.Add("ProjectActivated"); - if (!string.IsNullOrWhiteSpace(config.ModelDrawingPath)) AcadApplication.DocumentManager.Open(config.ModelDrawingPath, false); + if (!string.IsNullOrWhiteSpace(config.ModelDrawingPath)) automationOpenedDocuments.Add(AcadApplication.DocumentManager.Open(config.ModelDrawingPath, false)); transitions.Add("DocumentOpenStarted"); document = AcadApplication.DocumentManager.Open(config.OrthoDrawingPath, false); + automationOpenedDocuments.Add(document); AcadApplication.DocumentManager.MdiActiveDocument = document; transitions.Add("DocumentOpened"); document.CommandEnded += OnCommandEnded; @@ -315,12 +317,15 @@ internal sealed class FirstLimitedApplyAutomationCoordinator exitHandlerSubscribed = false; // Do not close a cached automation document and then reuse it. Resolve // the currently-live document only at the final quiescent callback. - var active = AcadApplication.DocumentManager.MdiActiveDocument; - if (active is not null) + // Only documents opened by this coordinator may be closed. Keep no + // document reference after closing it; DocumentDestroyed can run + // immediately as part of CloseAndDiscard. + foreach (var opened in automationOpenedDocuments.ToArray().Reverse()) { - transitions.Add("ActiveDocumentCloseRequested"); - active.CloseAndDiscard(); + transitions.Add("AutomationDocumentCloseRequested"); + opened.CloseAndDiscard(); } + automationOpenedDocuments.Clear(); // Application.Quit alone leaves the script host alive. Close the // live document first, then request normal application shutdown. transitions.Add("ApplicationQuitRequested"); diff --git a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs index 91658c3..b42f776 100644 --- a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs @@ -82,12 +82,13 @@ public sealed class FoundationTests Assert.Contains("PostStartupReady", source); } - [Fact] public void AutomationControlledExit_ClosesLiveDocumentThenQuitsFromIdle() + [Fact] public void AutomationControlledExit_ClosesOnlyAutomationDocumentsThenQuits() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); Assert.Contains("RequestControlledExit", source); Assert.Contains("AcadApplication.Idle += OnExitIdle", source); - Assert.Contains("active.CloseAndDiscard();", source); + Assert.Contains("automationOpenedDocuments", source); + Assert.Contains("opened.CloseAndDiscard();", source); Assert.Contains("AcadApplication.Quit();", source); } @@ -101,7 +102,7 @@ public sealed class FoundationTests [Fact] public void Idle_WhenActiveDocumentNull_DoesNotThrow() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); - Assert.Contains("if (active is not null)", source); + Assert.DoesNotContain("MdiActiveDocument", source[source.IndexOf("private void DispatchNativeQuit", StringComparison.Ordinal)..]); Assert.Contains("AcadApplication.Quit();", source); } @@ -143,7 +144,7 @@ public sealed class FoundationTests var controlledExit = source[source.IndexOf("private void RequestControlledExit", StringComparison.Ordinal)..]; Assert.DoesNotContain("document.CloseAndDiscard", controlledExit); Assert.Contains("Resolve", controlledExit, StringComparison.OrdinalIgnoreCase); - Assert.Contains("MdiActiveDocument", controlledExit); + Assert.DoesNotContain("MdiActiveDocument", controlledExit); } [Fact] public void FinalCommandCompletion_UsesItsQuiescentExitBoundary()