fix: keep the expand/collapse arrow out of the hidden zone on macOS 26 - #381
Open
antib0t wants to merge 1 commit into
Open
fix: keep the expand/collapse arrow out of the hidden zone on macOS 26#381antib0t wants to merge 1 commit into
antib0t wants to merge 1 commit into
Conversation
macOS 26 places status-item windows asynchronously: for roughly the first second after launch every button window still reports origin (0,0). The launch collapse ran on a flat 1s delay and decided whether the arrow sat to the right of the separator by comparing those origins, so the check reduced to 0 >= 0 and was trivially true. The bar collapsed before the order could be verified. Combined with a stale "NSStatusItem Preferred Position" that placed the arrow to the left of the separator, that collapse pushed the app's own control off-screen. The bar could no longer be reopened by clicking, because the arrow had been hidden inside the zone it just collapsed. - Treat unplaced geometry as unknown, never as a valid order. - Wait for real geometry before the launch collapse instead of a fixed delay. - Validate the saved item order before the status items are created and repair it around the separator. The separator keeps its slot, so the hidden zone stays where the user put it and only the misplaced items move. - Add a bounded rescue that expands and rebuilds the items if a collapse ever leaves the arrow off every screen. Refs dwarvesf#336
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.
fix: keep the expand/collapse arrow out of the hidden zone on macOS 26
Refs #336
Issue description
On macOS 26 the app launches, the icons collapse, and the expand/collapse arrow is nowhere in the menu bar. There is no control left to click, so the bar cannot be expanded again. From the outside this reads as "Hidden Bar stopped working", or as the app hanging on launch: the process is alive and the collapse worked, but the app has hidden its own only UI.
Quitting and relaunching does not help. The state that causes it lives in the app's defaults and survives an uninstall, so reinstalling does not help either.
Reproduced on macOS 26.5.2 (25F84), Apple silicon, single display. Present in v1.10 and in current
develop.Root cause
The launch collapse runs before the status items are placed
macOS 26 positions status-item windows asynchronously. For roughly the first second after launch,
NSStatusBarButton.window?.frame.originis still(0, 0)for every item.StatusBarControllercollapses on a flat 1s delay and gates that collapse onisBtnSeparateValidPosition, which compares those origins:return btnExpandCollapseX >= btnSeparateX // LTRWith both origins at zero the comparison is
0 >= 0, so the guard passes on geometry that does not exist yet. The app collapses without ever checking where its own arrow is.Measured on 26.5.2, same launch, one second apart:
A stale saved order puts the arrow inside the hidden zone
macOS persists each item's slot as
NSStatusItem Preferred Position <autosaveName>in the app's own defaults. A larger value places the item further left (verified on 26.5, see below). The app only works when the group reads, left to right: always-hidden, separator, arrow. The arrow has to stay to the right of the separator, because collapsing inflates the separator and pushes everything to its left off-screen.A real user's defaults, carried over from an earlier install:
1000 > 717, so the arrow sat to the left of the separator, inside the zone the separator hides. The collapse then swallowed the arrow along with the user's icons:
The order check exists precisely to prevent this. It never got the chance to run on real numbers, because of the race above.
One correction to #360 while I was in here: on 26.5.2 the hide mechanism itself is fine. The separator's requested 4112pt is honored exactly, and icons do move off-screen. Counting menu-bar windows via
CGWindowListCopyWindowInfoat layer 25: 22 visible without the app, 12 with it collapsed. Length inflation on 26 works.Direction of the preferred-position value
The repair depends on knowing which way the value points, so I measured it instead of assuming. Two status items, positions written before creation:
An item with no saved value is placed by creation order, outside every item that owns a slot. That is why a partially saved set is normal rather than corrupt, and why the repair treats a missing value as outermost.
Steps to reproduce
The app's own defaults are what trigger this, so the reproduction seeds them directly. It is the state a real user arrived at through ordinary Cmd-dragging.
Before this PR the icons collapse and the arrow is gone, with no way to expand the bar again from the menu bar. After this PR the arrow stays visible at the inner edge, the same icons collapse, and clicking the arrow expands them.
To restore your own arrangement afterwards, delete both keys and let macOS re-place the items.
Fix details
Three changes in
StatusBarController.swift.Unplaced geometry now counts as unknown rather than valid. A new
isMenuBarGeometryReadyrequires both button windows to report a non-zero origin, and both order checks gate on it. A check that cannot see the layout answers no.The launch collapse waits for real geometry.
collapseWhenGeometryReady()replaces the flatasyncAfter(.now() + 1), retrying every 0.25s up to 20 attempts. If geometry never arrives the collapse still runs, and the order check, now honest, refuses it.The saved order is validated and repaired before the items exist. macOS reads the slot when the item is created, so
StatusItemPositions.repairIfNeeded()runs as the first statement ofinit(), ahead of the firststatusItem()call. Nothing is rewritten while the order is sound.The repair anchors on the separator and never moves it. An earlier version of this patch reassigned all the slots in sorted order. It put the arrow back on screen and quietly broke hiding, because the separator's slot is what defines the hidden zone, and moving it un-hides everything parked there. So the separator keeps its slot and only the misplaced items move, one slot's width to their own side:
Finally, a bounded rescue. If a collapse ever leaves the arrow off every screen anyway,
rescueArrowIfSwallowed()expands the bar, pulls the arrow inside the separator, and rebuilds the two status items so the arrow returns in the same session instead of after a restart. It is capped at two attempts, so a repair that does not take cannot turn into collapse/rescue ping-pong.Two smaller things came along with it. The
HideMechanism:diagnostic now logs the arrow's frame and whether it intersects any screen, which is the signal this class of bug turns on. The.prefsChangedobserver moved out ofgetContextMenu()intoinit(), since the rescue path rebuilds the UI and would otherwise register a duplicate.Testing
macOS 26.5.2 (25F84), Xcode 26.6, Release build, single 2056pt display. Menu-bar icon counts come from
CGWindowListCopyWindowInfoat layer 25. Arrow visibility comes from the button window's frame checked againstNSScreen.screens.arrow=1000, sep=717arrow=667, arrow on screen, 9 icons hiddenarrow=900, sep=400arrowOnScreen=true, 13 visible each timeThe repair is idempotent. It fires once on a broken state and stays quiet afterwards.
Three things I could not cover, for lack of hardware: multi-display, RTL, and macOS 27. The RTL paths mirror the existing LTR comparisons and are written but untested.
NSApplication.shared.userInterfaceLayoutDirectionis read directly instead ofConstant.isUsingLTRLanguage, because the latter is only assigned inapplicationDidFinishLaunching, which runs after the status items are built.