Skip to content

macOS 27: detect hide-mechanism failure and degrade gracefully (#360)#370

Open
talkstream wants to merge 1 commit into
dwarvesf:developfrom
talkstream:bugfix/macos-27-detect-degrade
Open

macOS 27: detect hide-mechanism failure and degrade gracefully (#360)#370
talkstream wants to merge 1 commit into
dwarvesf:developfrom
talkstream:bugfix/macos-27-detect-degrade

Conversation

@talkstream

Copy link
Copy Markdown

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 real
macOS 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 a
runtime macOS-27 check.

Root cause (verified on hardware)

The trick (docs/ARCHITECTURE.md → "The core trick") inflates the separator
NSStatusItem.length to ~2× the screen width so the items to its left are pushed
off-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 on develop):

HideMechanism: requested=6016.0 windowWidth=5016.0 buttonWidth=5000.0 length=6016.0 screenWidth=3008.0 os=Version 27.0 (Build 26A5368g)
HideMechanism: degraded - macOS 27 hide unavailable (#360)

The separator's window grows to 5016ptwider than the 3008pt screen — yet
neighbours 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.md noted the bare window.frame.width is not a usable signal because
it 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 requested is unreliable — see the two captures
in #360 where windowWidth is 5016 regardless of requested):

button.window.frame.width meaning
macOS ≤ 26 ≈ screen width shared bar → inflation reflows neighbours (honored)
macOS 27 > screen width own window → inflation hides nothing (ignored)

evaluateHideOutcome returns .ignored when separatorWindow.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/.inconclusive is detected on macOS 27 (degradeHideUnavailable()):

  • resets the separator to its expanded length (no wide empty slot; isCollapsed
    derives from the length, so state stays consistent) and restores the arrow icon;
  • restores NSApp activation policy to .regular when "use full menu bar on
    expanding" is on (otherwise the bar shows while the app is stuck .accessory);
  • kills the auto-hide and hover timers so nothing re-enters the collapse path;
  • reveals a context-menu item and shows a one-time alert, both linking Osx 27 broken hidden bar #360;
  • on later launches, collapseMenuBar() degrades up front (gated on the persisted
    notice flag) so the separator never visibly inflates-then-snaps-back on every launch.

The three review fixes from docs/BACKLOG.md

  1. The hideMechanismChecked one-shot latch is set after the window is measured,
    so a transient nil window no longer burns the check.
  2. No ?? requested fallback — an unmeasurable outcome is .inconclusive and degrades;
    it is never coerced to "honored".
  3. degradeHideUnavailable() restores the activation policy under "use full menu bar
    on expanding".

macOS ≤ 26 is byte-identical

Every new branch is behind ProcessInfo.isMacOS27OrLater (a runtime check, so it
compiles on pre-27 SDKs). The only ≤ 26-visible changes are additive and inert:

  • two extra fields (screenWidth=, os=) on the existing HideMechanism NSLog;
  • one context-menu item created with isHidden = true (renders nothing until degrade).

No new entitlements, no private APIs, no Accessibility/Screen-Recording — the sandboxed,
permission-free posture in hidden/Hidden.entitlements is unchanged.

Validation

  • xcodebuild ... buildBUILD SUCCEEDED (only the pre-existing deprecated
    SMLoginItemSetEnabled warning).
  • Run on macOS 27 26A5368g: first collapse logs the HideMechanism capture above and
    degrades; subsequent launches degrade up front with no inflation (verified: no
    requested= line on later launches).
  • Snyk Code scan of hidden/: 0 issues.
  • plutil -lint hidden/en.lproj/Localizable.strings: OK.
  • Two-pass internal code review to zero medium+ findings.

I cannot exercise the ≤ 26 path (no ≤ 26 hardware), but it is unreachable by
construction via the version gate.

Notes

  • New user-facing strings are added to en.lproj only; other locales fall back to the
    (English) key. Translations welcome.
  • On the very first macOS-27 launch the separator briefly inflates before the degrade
    measures and reverts it (one frame, first run only); subsequent launches skip it.
  • Tracking issue Osx 27 broken hidden bar #360 is intentionally not auto-closed (per the maintainer's
    "issues stay open until UAT" policy).

Re #360. Groundwork for the managed-overflow redesign (#366).

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant