macOS 27: detect hide-mechanism failure and degrade gracefully (#360)#370
Open
talkstream wants to merge 1 commit into
Open
macOS 27: detect hide-mechanism failure and degrade gracefully (#360)#370talkstream wants to merge 1 commit into
talkstream wants to merge 1 commit into
Conversation
…warvesf#360) macOS 27 ("Golden Gate") re-architected the menu bar so inflating the separator NSStatusItem's length no longer pushes neighboring icons off-screen: the separator's own backing window grows wider than the screen while neighbors stay put, so collapsing silently hides nothing. Detect this on the first collapse (separator backing window wider than the screen) and degrade gracefully instead of pretending to hide: - stop inflating the separator and restore the bar to its expanded state - restore the app activation policy under "use full menu bar on expanding" - reveal a one-time context-menu notice + alert linking dwarvesf#360 - on later launches, skip the inflation up front so the bar never flashes The entire degrade is gated behind ProcessInfo.isMacOS27OrLater, so macOS <= 26 behavior is byte-identical (only the existing HideMechanism diagnostic log gains screenWidth/os fields). Implements the maintainer-documented "Option B (detect-and-degrade)" and the three required review fixes from docs/BACKLOG.md: move the one-shot latch after the measurement, never coerce an unmeasurable outcome to "honored", and restore the activation policy in the degrade path. Validated on real macOS 27 (build 26A5368g): HideMechanism: requested=6016 windowWidth=5016 buttonWidth=5000 screenWidth=3008 -> degraded No new entitlements or private APIs; sandbox posture unchanged.
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.
Summary
On macOS 27 ("Golden Gate") the core hide trick stopped working: the separator
hides its own bar, but the icons to its left stay visible (#360). This PR makes
Hidden Bar detect that hiding has become a no-op on macOS 27 and degrade
gracefully instead of silently pretending to work — exactly the "Option B
(detect-and-degrade)" path described in
docs/BACKLOG.md, now validated on realmacOS 27 hardware (build
26A5368g), which the backlog flagged as the blocker.This does not restore hiding on macOS 27 (no public API can — the length-inflation
approach is dead there); restoring real hiding is the separate managed-overflow
redesign (#366). What this PR fixes is the silent-no-op UX: the app now stops the
futile inflation, keeps the menu bar usable, and tells the user once what happened.
macOS <= 26 behaviour is byte-identical— the entire degrade is gated behind aruntime macOS-27 check.
Root cause (verified on hardware)
The trick (
docs/ARCHITECTURE.md→ "The core trick") inflates the separatorNSStatusItem.lengthto ~2× the screen width so the items to its left are pushedoff-screen. On macOS 27 the separator's own backing window still inflates, but it
no longer displaces neighbours — they keep their own layout, so collapsing hides
nothing.
Captured on macOS 27
26A5368g(the first-collapse diagnostic already ondevelop):The separator's window grows to
5016pt— wider than the3008ptscreen — yetneighbours stay put. On macOS ≤ 26 every status item shares one screen-wide menu-bar
window, so lengthening the item reflows the bar and pushes neighbours off-screen.
Detection signal
docs/BACKLOG.mdnoted the barewindow.frame.widthis not a usable signal becauseit reads the full menu-bar window width on ≤ 26. The stable discriminator is
window width vs. the screen width, not vs. the requested length (the system caps
the inflated window near ~5000pt, so
requestedis unreliable — see the two capturesin #360 where
windowWidthis5016regardless ofrequested):button.window.frame.widthevaluateHideOutcomereturns.ignoredwhenseparatorWindow.frame.width > screenWidth * 1.1.It is only ever consulted under the macOS-27 gate, so ≤ 26 never evaluates it. If a
future macOS 27.x restores the shared-window behaviour, the window stays screen-wide →
.honored→ no degrade.What the degrade does
When
.ignored/.inconclusiveis detected on macOS 27 (degradeHideUnavailable()):isCollapsedderives from the length, so state stays consistent) and restores the arrow icon;
NSAppactivation policy to.regularwhen "use full menu bar onexpanding" is on (otherwise the bar shows while the app is stuck
.accessory);collapseMenuBar()degrades up front (gated on the persistednotice flag) so the separator never visibly inflates-then-snaps-back on every launch.
The three review fixes from
docs/BACKLOG.mdhideMechanismCheckedone-shot latch is set after the window is measured,so a transient nil window no longer burns the check.
?? requestedfallback — an unmeasurable outcome is.inconclusiveand degrades;it is never coerced to "honored".
degradeHideUnavailable()restores the activation policy under "use full menu baron expanding".
macOS ≤ 26 is byte-identical
Every new branch is behind
ProcessInfo.isMacOS27OrLater(a runtime check, so itcompiles on pre-27 SDKs). The only ≤ 26-visible changes are additive and inert:
screenWidth=,os=) on the existingHideMechanismNSLog;isHidden = true(renders nothing until degrade).No new entitlements, no private APIs, no Accessibility/Screen-Recording — the sandboxed,
permission-free posture in
hidden/Hidden.entitlementsis unchanged.Validation
xcodebuild ... build→ BUILD SUCCEEDED (only the pre-existing deprecatedSMLoginItemSetEnabledwarning).26A5368g: first collapse logs theHideMechanismcapture above anddegrades; subsequent launches degrade up front with no inflation (verified: no
requested=line on later launches).hidden/: 0 issues.plutil -lint hidden/en.lproj/Localizable.strings: OK.I cannot exercise the ≤ 26 path (no ≤ 26 hardware), but it is unreachable by
construction via the version gate.
Notes
en.lprojonly; other locales fall back to the(English) key. Translations welcome.
measures and reverts it (one frame, first run only); subsequent launches skip it.
"issues stay open until UAT" policy).
Re #360. Groundwork for the managed-overflow redesign (#366).