refactor(hig): rebuild the menu bar on native AppKit menus - #2057
Merged
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
… the text-input key yield
datlechin
force-pushed
the
refactor/main-menu-appkit
branch
from
August 10, 2026 05:31
3432b76 to
228b6fd
Compare
datlechin
added a commit
that referenced
this pull request
Aug 10, 2026
This was referenced Aug 10, 2026
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.
Rebuilds the menu bar on native AppKit menus.
Why
Every editor window is a plain AppKit
NSWindow, never a SwiftUIScene. The menu bar was authored in SwiftUICommands, which has no access toNSUserInterfaceValidations, so it could not ask the key window's responder chain whether a command applies. Everything odd in the old menu code was a substitute for that missing call:CommandActionsRegistryexisted, by its own comment, because@FocusedValuecannot resolve for a window that is not a scene.resolvedCloseTabActionswalked three fallback tiers, including two linear scans, to guess what the responder chain already knows.keyWindowIsInspectorand the Undo/Redo titles readNSApp.keyWindowinside aCommandsbody, which SwiftUI does not track, so the values went stale.CommandFRouteexisted only because SwiftUI drops one of two items that claim the same shortcut.NSApp.sendAction(_:to:from:)by hand, re-implementing nil-target dispatch without the validation half.The correct pattern was already in the codebase three times and worked every time:
MainWindowToolbar+Validation,InspectorViewController, andKeyHandlingTableView. The only surface not using it was the only one that could not.What changed
Menu items are real
NSMenuItems withtarget = nil. AppKit resolves each one against the key window's responder chain and asks that responder whether it is enabled. Hand-written enabled flags are gone.TablePro/Core/Menu/holds one builder per menu plusMenuItemFactoryandMainMenuKeyEquivalentSync.MainSplitViewController+<Domain>MenuActions.swiftare one-line forwards into the existingMainContentCommandActions, which is unchanged.MainSplitViewController+MenuValidation.swiftsplits a pureMenuValidationContextandstatic isEnabled(_:context:)from a thin adapter, the same shapeMainWindowToolbar+Validationuses.Deleted:
AppMenuCommands,PasteboardCommands,CommandFRoute,ResponderChainActions,AppDelegate.showHelp, and roughly 700 lines fromTableProApp.swift.CommandActionsRegistrystays, reduced to what it actually is now: a lookup for two unfocused AI chat views. The menu no longer touches it.Added because they were missing: Minimize, Zoom, Move Tab to New Window, Show Toolbar, Customize Toolbar, New Table, New View.
Removed: Show Tables Sidebar and Show Favorites Sidebar (the sidebar's tab control does this), Show Object Icons and Show Object Comments (Settings and the sidebar's View Options do this), Open Project Folder (the welcome window does this).
Shortcut change
Cmd+Fnow always means Find. The filter bar moves toCmd+Option+F, which is what TablePlus, Postico, and Sequel Ace all use, and Focus Sidebar Filter moves toCtrl+Cmd+Option+F.KeyboardSettingsstores overrides only, so anyone who rebound these keeps their own binding.The old pair both defaulted to
Cmd+F, which is whatCommandFRoutewas working around.Verification
An unbundled harness confirmed the parts Apple does not document, in the same topology this app has (SwiftUI
Appplus@NSApplicationDelegateAdaptorplus a plainNSWindow):NSApp.mainMenuinapplicationDidFinishLaunchingsticks. SwiftUI does not reinstall its menu.MainMenuBuilderTestscovers menu order, title uniqueness (System Settings binds an App Shortcut by exact title), key equivalent uniqueness, everyShortcutActionreaching exactly one item, target nil-ness, and a validation truth table. All menu tests pass. Existing keyboard shortcut suites still pass.swiftlint --strictreports zero violations. Debug build succeeds.Review notes
The behaviour worth exercising by hand is enablement across window types: switch between an editor window, a CSV inspector window, and no window at all, and check that items dim and undim correctly, since that is exactly what the old menu got wrong.
Update: the deferred commands are in, and the disconnect work is absorbed
The five commands originally listed as follow-ups now ship here. The claim that they needed missing plumbing was wrong:
MainContentCommandActionsalready held the sidebar selection, so each needed aselectedObjectaccessor and a thin forward.Database menu now carries New Database, New Table, New View, Show Table Structure, Edit View Definition, Table Maintenance, Truncate Table, Disconnect, and Reconnect. Table Maintenance is a delegate-driven submenu, filled on
menuNeedsUpdate, because the operations depend on the driver and the selection.This branch rebased onto the merged disconnect work and simplified it.
ConnectionDisconnectAction,requestDisconnect()andretryConnection()are kept as-is. Deleted, because responder-chain validation makes them redundant:ConnectionWindowCommandState,CommandActionsRegistry.connectionWindow,publishConnectionCommandState(),clearConnectionCommandStateIfOwned(), the owner-identity guard, and theNSApp.keyWindow?.contentViewController as?lookup its menu code used. Its changelog entry anddocs/features/tabs.mdxwere corrected in place rather than with a Fixed entry, since both are unreleased.Update: audit findings
A five-lens audit ran over the result, each finding checked by two independent skeptics. 13 claims were refuted; the rest were fixed. The two that mattered:
Undo and Redo recursed until the stack overflowed.
MainSplitViewController.undo(_:)forwarded toundoChange(), which still contained aNSApp.sendAction("undo:", to: nil)probe from the SwiftUI era. That probe restarts target resolution at the key window's first responder, which now resolves back toMainSplitViewController. On main the probe was safe because nothing in the connection window implementedundo:; adding the responder method closed the loop. Cmd+Z with the grid focused would crash. The probes are gone, and Undo/Redo now validate against the live undo manager and take their title from it.Cmd+T could crash on a window that was not connected.
newWindowForTab:resolves toNSWindow, so the split view controller's validation never saw it and the item stayed enabled while connecting, failed, or disconnected.EditorWindownow validates it.Also fixed: the text-input key-equivalent yield was written but never wired, so a focused text field would have kept losing Cmd+Delete to the menu; three View toggles never flipped their title; Integrations opened Settings instead of the Integrations window; Copy Rows resolved against a different responder than its two siblings; and switching menu titles to a Unicode ellipsis had orphaned 99 already-translated strings, so they are back on the catalog's existing form.
Removed as dead:
dataGridShortcut,AppDelegate.newWindowForTab,menuChangeManager, an emptyifleft by the disconnect cleanup, and an app-wideNSWindowextension that had one caller.