Keep the window title current, and ask before the window closes (#116) - #132
Merged
Conversation
The last two acceptance criteria on #116, unblocked by ktsu-dev/ImGuiApp#341 (released as 3.16.0): the config's Title is init-only and read once at window creation, and there was no cancellable close hook, so neither could be done from here. The title now shows the open document and a trailing "*" while it has unsaved changes, refreshed each frame. SetWindowTitle skips the write when the title is unchanged, so that costs a string comparison rather than a window call. The dirty state is read from the undo service through the existing HasUnsavedChanges, not tracked a second time. The menu-bar indicator stays: it is not redundant, because a maximised window's title bar is easy to overlook and a tiling window manager may not draw one at all. Closing the window now raises the same Save / Discard / Cancel prompt that New and Open already use, rather than discarding the work silently. Three things about that hook are worth knowing, because each is a way to get it wrong: The prompt cannot be drawn from the callback - it returns before the next frame is rendered - so a close with unsaved work only records the request and refuses. The next frame raises the prompt, and only the user's answer closes for real. The callback also fires for ImGuiApp.Stop, which is how the confirmed exit closes. Without an explicit exitConfirmed latch the editor would veto its own agreed exit forever: discarding does not clear the unsaved flag, so the condition that refused the first close is still true once the user has said to close anyway. That would have been an unquittable window. A second close while the prompt is up would otherwise stack another prompt, so the request is latched until the first is answered. The guard grew an optional onCancel for that - it is the only caller that needs to know the user backed out; New and Open simply do nothing. The entry point moves to its own Program.cs. Adding OnClosing tipped SchemaEditor over the CA1506 class-coupling limit, and the honest fix was to take out what does not belong: the configuration names a windowing framework and a delegate type per callback, none of which is part of editing a schema. That is what the analyser was pointing at, so the fix follows it rather than suppressing it, and the host's whole contract with ImGuiApp is now readable in one place. OnStart, OnTick, OnRender, OnMenu and ShouldClose become internal so the entry point can bind them. The 299 tests still pass on net8.0, net9.0 and net10.0, and the solution builds against 3.16.0 - which is what proves the released package really carries the new API. The editor wiring itself is NOT covered: SchemaEditor has no tests and no harness to write them against, which is issue #128. What is verified here is that it compiles and that nothing else regressed; the title and prompt behaviour has not been executed by a test. Closes #116 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WmzGm9XniSoqaiVGDT6qT
The quality gate passed with one new issue: S3358, a nested ternary in the title expression - the dirty marker's ternary sat inside the has-a-document one. Splitting the null case into a guard clause and naming the marker reads better anyway, and the interpolated string no longer has to be parsed inside out. Verified locally rather than taken on trust. Probing the editor with SonarAnalyzer normally cannot compile at all: the referenced library's pre-existing findings are promoted to errors by the SDK's editorconfig, so the build stops before the editor is analysed. Suppressing exactly the library's known baseline rules for the probe lets the editor compile, and it reported this finding at SchemaEditor.Files.cs(307,24). After the change it is gone, and the four that remain are pre-existing findings in files this PR does not touch - two in ButtonTree.cs, two in SchemaEditor.cs. 299 tests still pass and the solution builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WmzGm9XniSoqaiVGDT6qT
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Finishes #116. The last two acceptance criteria were blocked by
ktsu.ImGui.App—ImGuiAppConfig.Titleisinit-only and read once at window creation, and there was no cancellable close hook. I added both upstream in ktsu-dev/ImGuiApp#341, now released as 3.16.0, which this bumps to.Live window title
Shows the open document plus a trailing
*while it has unsaved changes, refreshed each frame.SetWindowTitleskips the write when the title is unchanged, so that costs a string comparison per frame rather than a window call.The dirty state comes from the undo service via the existing
HasUnsavedChanges— not tracked a second time.The menu-bar indicator stays. It isn't redundant: a maximised window's title bar is easy to overlook, and a tiling window manager may not draw one at all.
Close prompt
Closing the window now raises the same Save / Discard / Cancel prompt New and Open already use, instead of discarding the work silently.
Three things about the hook are worth a reviewer's attention, because each is a way to get it wrong:
The prompt can't be drawn from the callback — it returns before the next frame is rendered. So a close with unsaved work only records the request and refuses; the next frame raises the prompt, and only the user's answer closes for real.
The callback also fires for
ImGuiApp.Stop, which is how the confirmed exit closes. Without the explicitexitConfirmedlatch the editor would veto its own agreed exit forever — discarding doesn't clear the unsaved flag, so the condition that refused the first close is still true once the user has said to close anyway. That would have been an unquittable window, and it's the one bug in this change I'd most want caught.A second close while the prompt is up would otherwise stack another prompt, so the request is latched until the first is answered.
WithUnsavedChangesGuardgrew an optionalonCancelfor that — the close path is the only caller that needs to know the user backed out; New and Open simply do nothing.Why the entry point moved
Adding
OnClosingtippedSchemaEditorover the CA1506 class-coupling limit (96, limit 95). I checked what actually cost the point rather than guessing: it's theFunc<bool>the assignment introduces.The honest fix was to take out what doesn't belong.
Mainand theImGuiAppConfignow live inSchemaEditor/Program.cs— that configuration names a windowing framework and a delegate type per callback, none of which is part of editing a schema. So the fix follows what the analyser was pointing at rather than suppressing it, and the host's whole contract with ImGuiApp is readable in one place.OnStart,OnTick,OnRender,OnMenuandShouldClosebecomeinternalso the entry point can bind them.Verification, and its limit
299 tests pass on net8.0, net9.0 and net10.0, and the solution builds against 3.16.0 — which is what proves the released package really carries the new API, rather than my assuming it from the merge.
The editor wiring itself is not covered by any test.
SchemaEditorhas no tests and no harness to write them against — that's #128. What's verified here is that it compiles and that nothing else regressed; the title updates and the close prompt have not been executed by a test. I'd rather say that plainly than let "299 tests pass" imply more than it does.That's also the strongest argument yet for #128: this change adds exactly the kind of stateful, sequence-dependent logic (a latch, a deferred prompt, a re-entrancy guard) that a headless harness would catch regressions in.
ktsu.ImGui.Appships anImGui.App.Testingpackage built for this.Closes #116
🤖 Generated with Claude Code
https://claude.ai/code/session_012WmzGm9XniSoqaiVGDT6qT
Generated by Claude Code