@@ -34,6 +34,18 @@ extension StatusItemVisibilitySnapshot: CustomStringConvertible {
3434 }
3535}
3636
37+ struct StatusItemStartupVisibilityEvidence : Equatable , CustomStringConvertible {
38+ let autosaveName : String
39+ let expectsVisibility : Bool
40+ let visibilityDefault : Bool ?
41+ let snapshot : StatusItemVisibilitySnapshot
42+
43+ var description : String {
44+ " name= \( self . autosaveName) ,expected= \( self . expectsVisibility) , "
45+ + " default= \( self . visibilityDefault. map ( String . init) ?? " unset " ) , \( self . snapshot) "
46+ }
47+ }
48+
3749@MainActor
3850func isStatusItemBlocked( _ item: NSStatusItem ) -> Bool {
3951 MenuBarVisibilityWatcher . isBlockedSnapshot ( snapshot: MenuBarVisibilityWatcher . visibilitySnapshot ( item) )
@@ -113,14 +125,20 @@ enum MenuBarVisibilityWatcher {
113125
114126 static func hasAnyStartupRecoveryCandidate(
115127 snapshots: [ StatusItemVisibilitySnapshot ] ,
128+ evidence: [ StatusItemStartupVisibilityEvidence ] = [ ] ,
116129 windowSnapshots: [ MenuBarStatusItemWindowSnapshot ] = [ ] ,
117- detectTahoeBlockedProxy : Bool = false )
130+ detectTahoeBlockedStatusItem : Bool = false )
118131 -> Bool
119132 {
120133 if self . hasAnyBlockedVisibleSnapshot ( snapshots) {
121134 return true
122135 }
123- guard detectTahoeBlockedProxy,
136+ if detectTahoeBlockedStatusItem,
137+ self . hasAnyTahoeHiddenNoProxyCandidate ( evidence: evidence, windowSnapshots: windowSnapshots)
138+ {
139+ return true
140+ }
141+ guard detectTahoeBlockedStatusItem,
124142 self . hasAnyDisplacedVisibleSnapshot ( snapshots) ,
125143 windowSnapshots. contains ( where: \. isTahoeBlockedProxy)
126144 else {
@@ -129,6 +147,24 @@ enum MenuBarVisibilityWatcher {
129147 return true
130148 }
131149
150+ static func hasAnyTahoeHiddenNoProxyCandidate(
151+ evidence: [ StatusItemStartupVisibilityEvidence ] ,
152+ windowSnapshots: [ MenuBarStatusItemWindowSnapshot ] )
153+ -> Bool
154+ {
155+ evidence. contains { item in
156+ // Tahoe can destroy the Control Center scene while leaving its enabled default behind.
157+ // Requiring both app intent and that default avoids treating ordinary hidden items as blocked.
158+ item. expectsVisibility
159+ && item. visibilityDefault == true
160+ && !item. snapshot. isVisible
161+ && !item. snapshot. hasWindow
162+ && !windowSnapshots. contains {
163+ $0. name == item. autosaveName && $0. isOnscreen && $0. isWithinDisplayBounds
164+ }
165+ }
166+ }
167+
132168 @MainActor
133169 static func visibilitySnapshots( _ items: [ NSStatusItem ] ) -> [ StatusItemVisibilitySnapshot ] {
134170 items. map { item in
@@ -145,15 +181,17 @@ enum MenuBarVisibilityWatcher {
145181 appLaunchedAt: Date ,
146182 now: Date = Date ( ) ,
147183 snapshots: [ StatusItemVisibilitySnapshot ] ,
184+ evidence: [ StatusItemStartupVisibilityEvidence ] = [ ] ,
148185 windowSnapshots: [ MenuBarStatusItemWindowSnapshot ] = [ ] ,
149- detectTahoeBlockedProxy : Bool = false )
186+ detectTahoeBlockedStatusItem : Bool = false )
150187 -> Bool
151188 {
152189 guard now. timeIntervalSince ( appLaunchedAt) <= self . startupFreshnessInterval else { return false }
153190 return self . hasAnyStartupRecoveryCandidate (
154191 snapshots: snapshots,
192+ evidence: evidence,
155193 windowSnapshots: windowSnapshots,
156- detectTahoeBlockedProxy : detectTahoeBlockedProxy )
194+ detectTahoeBlockedStatusItem : detectTahoeBlockedStatusItem )
157195 }
158196
159197 static func shouldRefreshScreenChangePlacement(
@@ -215,14 +253,16 @@ extension StatusItemController {
215253 }
216254
217255 private func checkStartupStatusItemVisibility( appLaunchedAt: Date , now: Date = Date ( ) ) {
218- let snapshots = MenuBarVisibilityWatcher . visibilitySnapshots ( self . startupVisibilityStatusItems)
256+ let evidence = self . startupStatusItemVisibilityEvidence ( )
257+ let snapshots = evidence. map ( \. snapshot)
219258 let windowSnapshots = self . statusItemWindowSnapshots ( )
220259 guard MenuBarVisibilityWatcher . shouldAttemptStartupRecovery (
221260 appLaunchedAt: appLaunchedAt,
222261 now: now,
223262 snapshots: snapshots,
263+ evidence: evidence,
224264 windowSnapshots: windowSnapshots,
225- detectTahoeBlockedProxy : self . canDetectTahoeBlockedProxy )
265+ detectTahoeBlockedStatusItem : self . canDetectTahoeBlockedStatusItem )
226266 else {
227267 return
228268 }
@@ -231,18 +271,21 @@ extension StatusItemController {
231271 " Status item failed to materialize or remained detached; recreating status items " ,
232272 metadata: [
233273 " snapshots " : snapshots. map ( \. description) . joined ( separator: " | " ) ,
274+ " evidence " : evidence. map ( \. description) . joined ( separator: " | " ) ,
234275 " windows " : self . statusItemWindowDiagnosticsDescription ( windowSnapshots) ,
235276 ] )
236277 self . recreateStatusItemsForVisibilityRecovery ( )
237278
238- let recoveredSnapshots = MenuBarVisibilityWatcher . visibilitySnapshots ( self . startupVisibilityStatusItems)
279+ let recoveredEvidence = self . startupStatusItemVisibilityEvidence ( )
280+ let recoveredSnapshots = recoveredEvidence. map ( \. snapshot)
239281 let recoveredWindowSnapshots = self . statusItemWindowSnapshots ( )
240282 guard MenuBarVisibilityWatcher . shouldAttemptStartupRecovery (
241283 appLaunchedAt: appLaunchedAt,
242284 now: now,
243285 snapshots: recoveredSnapshots,
286+ evidence: recoveredEvidence,
244287 windowSnapshots: recoveredWindowSnapshots,
245- detectTahoeBlockedProxy : self . canDetectTahoeBlockedProxy )
288+ detectTahoeBlockedStatusItem : self . canDetectTahoeBlockedStatusItem )
246289 else {
247290 self . menuLogger. info (
248291 " Status item materialized after recreation " ,
@@ -254,6 +297,7 @@ extension StatusItemController {
254297 " Status item still unavailable after recreation " ,
255298 metadata: [
256299 " snapshots " : recoveredSnapshots. map ( \. description) . joined ( separator: " | " ) ,
300+ " evidence " : recoveredEvidence. map ( \. description) . joined ( separator: " | " ) ,
257301 " windows " : self . statusItemWindowDiagnosticsDescription ( recoveredWindowSnapshots) ,
258302 ] )
259303 guard #available( macOS 26 . 0 , * ) ,
@@ -382,7 +426,20 @@ extension StatusItemController {
382426 [ self . statusItem] + Array( self . statusItems. values)
383427 }
384428
385- private var canDetectTahoeBlockedProxy : Bool {
429+ private func startupStatusItemVisibilityEvidence( ) -> [ StatusItemStartupVisibilityEvidence ] {
430+ self . startupVisibilityStatusItems. map { item in
431+ let autosaveName = item. autosaveName ?? " "
432+ return StatusItemStartupVisibilityEvidence (
433+ autosaveName: autosaveName,
434+ expectsVisibility: self . expectedVisibleStatusItemAutosaveNames. contains ( autosaveName) ,
435+ visibilityDefault: MenuBarStatusItemDefaultsRepair . visibilityDefault (
436+ defaults: self . settings. userDefaults,
437+ autosaveName: autosaveName) ,
438+ snapshot: MenuBarVisibilityWatcher . visibilitySnapshot ( item) )
439+ }
440+ }
441+
442+ private var canDetectTahoeBlockedStatusItem : Bool {
386443 if #available( macOS 26 . 0 , * ) {
387444 return true
388445 }
0 commit comments