diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs index 222914d..a2b9562 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs @@ -234,8 +234,8 @@ internal sealed class FirstLimitedApplyAutomationCoordinator transitions.Add("CommandCompleted:" + name); if (step++ == 0) { after1 = Dbmod(); counts1 = Counts(); Queue("PXP-OA"); return; } if (step == 2 && string.Equals(config?.Phase, "Apply", StringComparison.OrdinalIgnoreCase)) { after2 = Dbmod(); counts2 = Counts(); Queue("_.UNDO _1"); return; } - if (string.Equals(config?.Phase, "Apply", StringComparison.OrdinalIgnoreCase)) { afterUndo = Dbmod(); countsUndo = Counts(); Complete(true, null); } - else { after2 = Dbmod(); counts2 = Counts(); Complete(true, null); } + if (string.Equals(config?.Phase, "Apply", StringComparison.OrdinalIgnoreCase)) { afterUndo = Dbmod(); countsUndo = Counts(); Complete(true, null, safeToExitNow: true); } + else { after2 = Dbmod(); counts2 = Counts(); Complete(true, null, safeToExitNow: true); } }); } private void OnCommandFailed(object sender, CommandEventArgs e) => RunCallback("CommandFailed", () => Complete(false, "AUTOMATION_COMMAND_FAILED:" + e.GlobalCommandName)); @@ -274,7 +274,7 @@ internal sealed class FirstLimitedApplyAutomationCoordinator throw new InvalidOperationException("AUTOMATION_FIXTURE_OUTSIDE_MARKER_ROOT"); return loaded; } - private void Complete(bool success, string? error) + private void Complete(bool success, string? error, bool safeToExitNow = false) { if (IsTerminal) return; Transition(success ? CoordinatorState.Completed : CoordinatorState.Failed, success ? "Completed" : "Failed"); @@ -282,7 +282,7 @@ internal sealed class FirstLimitedApplyAutomationCoordinator TryWriteEvidence(success, error); selectedTargetSnapshotId = null; pxpResults.Clear(); - RequestControlledExit(); + RequestControlledExit(safeToExitNow); } // AutoCAD's direct Application.Quit can leave a startup-script host alive. 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 edb5bd2..91658c3 100644 --- a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs @@ -146,6 +146,13 @@ public sealed class FoundationTests Assert.Contains("MdiActiveDocument", controlledExit); } + [Fact] public void FinalCommandCompletion_UsesItsQuiescentExitBoundary() + { + var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); + Assert.Contains("Complete(true, null, safeToExitNow: true)", source); + Assert.Contains("RequestControlledExit(safeToExitNow);", source); + } + [Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs"))); [Fact] public void LegacyAliases_AreRegistered() => Assert.True(File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-AAV") && File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-OA")); [Fact] public void RibbonCommand_ReferencesRegisteredCommand() => Assert.Contains("PHP_REC_ANNOT ", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));