fix(coordinator): make the window title a function of the pane it is showing - #2054
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
The window title was the one part of the connection window that never became a function of its phase. Content and chrome already are, since #2053. The title was still decided by whoever wrote to it last.
Found by auditing every title path across four lenses (window lifecycle, compliance with the "Window tab titles" invariant in CLAUDE.md, behaviour under translation, test coverage), with each finding independently attacked before being accepted. 17 survived, 5 were refuted.
What was wrong
A connecting window was called "SQL Query".
MainSplitViewController.initresolved the title from a payload that describes no tab, soresolveTitlefell through every tier tofallbackTitle, which is literally the string "SQL Query". The window was named after a document it did not have.A window that lost its session kept the name of the table it had stopped showing.
applyPhase()repaints content and chrome and never touched the title, and the only ongoing title sink lives insideMainContentView, which is unmounted the moment the pane stops being.content. The pane would read "Connection closed" while the window and its native tab label still saidorders. Reachable through exhausted tunnel recovery, which is easy to hit after waking from sleep, and through an MCP disconnect.Placeholder detection compared a title against English text.
adoptSessiondecided a title was disposable by testingwindowTitle == WindowTitleResolver.fallbackTitle || windowTitle.hasSuffix(" Query"). Both halves compare against the rendering of a localized string, so the whole branch is dead in every translated build. In English it was worse than dead: a tab a user named "Weekly Query" was silently renamed on every reconnect.The titlebar repeated itself. That same branch set title and subtitle to the same value, and a second site set the title to the connection name while the subtitle was already the connection name. Hence "TablePro - TablePro".
The no-tabs rule existed twice and the two copies disagreed, in
MainContentView.onAppearand inupdateWindowTitleAndFileState().Renaming a connection never reached an open window, because the window captured its connection record once at creation.
The fix
One resolver entry point decides title and subtitle together:
Not showing content, or showing content with no tabs, resolves to the connection name with no subtitle. Showing a tab resolves to the tab's title with its database binding as subtitle, suppressed when it would equal the title. Deciding both together is what makes the repetition impossible by construction rather than by a guard someone has to remember.
applyPhase()gains a title sink beside content and chrome, so a phase change repaints the name.MainSplitViewController.initcalls it aftersuper.init, which keeps the invariant that a native tab label is correct at creation rather than at first activation, since AppKit draws labels for tabs that are never activated.The string-matching block in
adoptSessionis deleted rather than repaired. So is the duplicated no-tabs rule inonAppearand the.newEmptyTab-only title override, which is why a query tab opened from a link or from AI used to keep "SQL Query" while the tab itself read "Query 3": the resolver now reads the real selected tab whatever the intent was.payloadConnectionis re-read when the connection record changes, so a rename reaches both the window name and the connecting screen.Not a rewrite
WindowTitleResolver's existing tab logic is untouched and its 47 tests still pass. It was never what was broken. The defect was that consumers made title decisions the resolver did not own.Testing
swiftlint --strictclean across 1291 files. Build succeeds.WindowTitleResolverWindowTestsadds 7 tests covering what no test covered before, since every previous test exercised the pure resolver and nothing exercised a consumer-side decision: every non-content pane resolves to the connection name, restored tabs do not leak into the name of a window that is still connecting, an empty content window carries no subtitle, the subtitle never repeats the title, blank and missing connections still produce a usable name, and a tab a user named themselves survives a reconnect.Deferred
One confirmed finding is left out on purpose. New query tab names are the only tab titles never run through localization, and the counter behind "Query 3" is recovered by parsing the text of existing tab titles (
QueryTabManager.swift:107). That is the same class of defect as the placeholder matcher fixed here, but it belongs to tab naming rather than window naming and deserves its own change.