diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs index 79f5b50..6164b18 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs @@ -287,9 +287,10 @@ internal sealed class FirstLimitedApplyAutomationCoordinator RequestControlledExit(safeToExitNow); } - // AutoCAD's direct Application.Quit can leave a startup-script host alive. - // Dispatching the native QUIT command from the next quiescent Idle cycle - // preserves normal command shutdown semantics. + // The managed Application.Quit call can leave a startup-script host alive + // when it is invoked from a CommandEnded callback. Queue AutoCAD's native + // QUIT command instead; the command processor runs it after the current + // public PXP-OA command has fully returned and discards every open drawing. private void RequestControlledExit(bool idleSafe = false) { if (exitQueued) return; @@ -315,21 +316,14 @@ internal sealed class FirstLimitedApplyAutomationCoordinator { if (exitHandlerSubscribed) AcadApplication.Idle -= OnExitIdle; exitHandlerSubscribed = false; - // Do not close a cached automation document and then reuse it. Resolve - // the currently-live document only at the final quiescent callback. - // 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("AutomationDocumentCloseRequested"); - opened.CloseAndDiscard(); - } + if (document is null) throw new InvalidOperationException("AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE"); + transitions.Add("NativeQuitCommandQueued"); + // Persist the exit boundary before handing control back to AutoCAD; + // process termination can otherwise prevent the final evidence flush. + TryWriteEvidence(state == CoordinatorState.Completed, null); + document.SendStringToExecute("_.QUIT _N ", true, false, false); + document = null; automationOpenedDocuments.Clear(); - // Application.Quit alone leaves the script host alive. Close the - // live document first, then request normal application shutdown. - transitions.Add("ApplicationQuitRequested"); - AcadApplication.Quit(); } catch (System.Exception exception) { 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 b42f776..5c4d0e7 100644 --- a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs @@ -82,14 +82,14 @@ public sealed class FoundationTests Assert.Contains("PostStartupReady", source); } - [Fact] public void AutomationControlledExit_ClosesOnlyAutomationDocumentsThenQuits() + [Fact] public void AutomationControlledExit_QueuesNativeQuitAfterPublicCommand() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); Assert.Contains("RequestControlledExit", source); Assert.Contains("AcadApplication.Idle += OnExitIdle", source); - Assert.Contains("automationOpenedDocuments", source); - Assert.Contains("opened.CloseAndDiscard();", source); - Assert.Contains("AcadApplication.Quit();", source); + Assert.Contains("NativeQuitCommandQueued", source); + Assert.Contains("document.SendStringToExecute(\"_.QUIT _N \", true, false, false);", source); + Assert.DoesNotContain("AcadApplication.Quit();", source); } [Fact] public void Callback_AfterDocumentDestroyed_DoesNotAccessDocument() @@ -103,7 +103,7 @@ public sealed class FoundationTests { var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); Assert.DoesNotContain("MdiActiveDocument", source[source.IndexOf("private void DispatchNativeQuit", StringComparison.Ordinal)..]); - Assert.Contains("AcadApplication.Quit();", source); + Assert.Contains("AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE", source); } [Fact] public void TerminalState_IgnoresFurtherCallbacks() @@ -143,8 +143,8 @@ public sealed class FoundationTests var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); var controlledExit = source[source.IndexOf("private void RequestControlledExit", StringComparison.Ordinal)..]; Assert.DoesNotContain("document.CloseAndDiscard", controlledExit); - Assert.Contains("Resolve", controlledExit, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("MdiActiveDocument", controlledExit); + Assert.Contains("document = null;", controlledExit); } [Fact] public void FinalCommandCompletion_UsesItsQuiescentExitBoundary()