Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,37 @@ import Darwin

final class MainWindowHostingView<Content: View>: NSHostingView<Content> {
private let zeroSafeAreaLayoutGuide = NSLayoutGuide()
private let usesSystemSafeArea: Bool

override var safeAreaInsets: NSEdgeInsets { NSEdgeInsetsZero }
override var safeAreaRect: NSRect { bounds }
override var safeAreaLayoutGuide: NSLayoutGuide { zeroSafeAreaLayoutGuide }
override var safeAreaInsets: NSEdgeInsets {
usesSystemSafeArea ? super.safeAreaInsets : NSEdgeInsetsZero
}
override var safeAreaRect: NSRect {
usesSystemSafeArea ? super.safeAreaRect : bounds
}
override var safeAreaLayoutGuide: NSLayoutGuide {
usesSystemSafeArea ? super.safeAreaLayoutGuide : zeroSafeAreaLayoutGuide
}

required init(rootView: Content) {
if #available(macOS 26.0, *) {
// On macOS 26, use system safe area so:
// - Sidebar (.ignoresSafeArea) extends under the glass titlebar
// - Terminal content respects the titlebar and stays below it
self.usesSystemSafeArea = true
} else {
self.usesSystemSafeArea = false
}
super.init(rootView: rootView)
addLayoutGuide(zeroSafeAreaLayoutGuide)
NSLayoutConstraint.activate([
zeroSafeAreaLayoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor),
zeroSafeAreaLayoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor),
zeroSafeAreaLayoutGuide.topAnchor.constraint(equalTo: topAnchor),
zeroSafeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor),
])
if !usesSystemSafeArea {
addLayoutGuide(zeroSafeAreaLayoutGuide)
NSLayoutConstraint.activate([
zeroSafeAreaLayoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor),
zeroSafeAreaLayoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor),
zeroSafeAreaLayoutGuide.topAnchor.constraint(equalTo: topAnchor),
zeroSafeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}

@available(*, unavailable)
Expand Down Expand Up @@ -2581,6 +2598,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
syncMenuBarExtraVisibility()
updateController.startUpdaterIfNeeded()
}
// Start the titlebar accessory controller on all versions so the
// notifications popover infrastructure is available. On macOS 26
// the visual titlebar items come from SwiftUI .toolbar, but the
// popover is still managed by the accessory controller.
titlebarAccessoryController.start()
windowDecorationsController.start()
installMainWindowKeyObserver()
Expand Down Expand Up @@ -7084,10 +7105,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
window.collectionBehavior.insert(.fullScreenDisallowsTiling)
}
window.title = ""
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
if #available(macOS 26.0, *) {
// On macOS 26+, let the system render the native glass titlebar
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = false
} else {
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
}
window.isMovableByWindowBackground = false
window.isMovable = false
if #available(macOS 26.0, *) {
window.isMovable = true
} else {
window.isMovable = false
}
let restoredFrame = resolvedWindowFrame(from: sessionWindowSnapshot)
if let restoredFrame {
window.setFrame(restoredFrame, display: false)
Expand Down Expand Up @@ -10119,6 +10150,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
#endif

func attachUpdateAccessory(to window: NSWindow) {
if #available(macOS 26.0, *) {
// On macOS 26, toolbar buttons are native SwiftUI .toolbar items
// in the NavigationSplitView. Skip attaching the old titlebar
// accessory views, but the controller is already started (for
// notifications popover support).
return
}
titlebarAccessoryController.start()
titlebarAccessoryController.attach(to: window)
}
Comment on lines 10152 to 10162
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Bell button is a no-op on macOS 26

attachUpdateAccessory(to:) returns early without calling titlebarAccessoryController.start() or .attach(to:), and applicationDidFinishLaunching also skips .start() on macOS 26 (~line 2601). As a result controlsControllers.allObjects is always empty. When the SwiftUI toolbar bell calls AppDelegate.shared?.toggleNotificationsPopover(animated: true), UpdateTitlebarAccessoryController.toggleNotificationsPopover bails immediately on guard !controllers.isEmpty else { return } — clicking the bell silently does nothing.

Fix: either call titlebarAccessoryController.start() (without attach) so the popover infrastructure is initialized, or route the bell action through a macOS-26-specific popover path that does not depend on controlsControllers.

Expand All @@ -10127,7 +10165,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
windowDecorationsController.apply(to: window)
}

/// Notification posted on macOS 26 to toggle the SwiftUI notifications popover.
static let toggleNotificationsPopoverNotification = Notification.Name("cmux.toggleNotificationsPopover")

func toggleNotificationsPopover(animated: Bool = true, anchorView: NSView? = nil) {
if #available(macOS 26.0, *) {
NotificationCenter.default.post(name: Self.toggleNotificationsPopoverNotification, object: nil)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notification popover broadcast toggles all windows simultaneously

Medium Severity

On macOS 26, toggleNotificationsPopover() posts toggleNotificationsPopoverNotification to NotificationCenter.default with no window-scoping information. Every ContentView instance subscribes via .onReceive and toggles its own isNotificationsPopoverPresented state. In a multi-window setup, triggering the notification shortcut or fullscreen controls bell button causes ALL windows' popovers to toggle simultaneously instead of only the active window's.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eba5d8c. Configure here.

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notifications popover state queries broken on macOS 26

High Severity

On macOS 26, dismissNotificationsPopoverIfShown() and isNotificationsPopoverShown() incorrectly delegate to titlebarAccessoryController. As the notifications popover is SwiftUI-managed on this OS, these functions don't reflect its actual state, breaking Escape key dismissal and allowing typing to leak into the terminal.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eba5d8c. Configure here.

titlebarAccessoryController.toggleNotificationsPopover(animated: animated, anchorView: anchorView)
Comment on lines +10168 to 10176
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scope and complete the macOS 26 notifications presenter.

This bridge only emits a global toggle and drops the originating window. The SwiftUI side therefore has no deterministic target window, and the explicit show/dismiss/state entry points in this file (showNotificationsPopoverFromMenuBar, dismissNotificationsPopoverIfShown, isNotificationsPopoverShown) still talk to titlebarAccessoryController. On macOS 26 that can desync menu-bar/Escape behavior from the popover that is actually visible.

🛠️ Suggested direction
     /// Notification posted on macOS 26 to toggle the SwiftUI notifications popover.
     static let toggleNotificationsPopoverNotification = Notification.Name("cmux.toggleNotificationsPopover")
+    static let showNotificationsPopoverNotification = Notification.Name("cmux.showNotificationsPopover")
+    static let dismissNotificationsPopoverNotification = Notification.Name("cmux.dismissNotificationsPopover")
+    static let notificationsPopoverWindowIdUserInfoKey = "windowId"

     func toggleNotificationsPopover(animated: Bool = true, anchorView: NSView? = nil) {
         if `#available`(macOS 26.0, *) {
-            NotificationCenter.default.post(name: Self.toggleNotificationsPopoverNotification, object: nil)
+            guard
+                let window = anchorView?.window ?? NSApp.keyWindow ?? NSApp.mainWindow,
+                let windowId = mainWindowId(for: window)
+            else { return }
+            NotificationCenter.default.post(
+                name: Self.toggleNotificationsPopoverNotification,
+                object: window,
+                userInfo: [Self.notificationsPopoverWindowIdUserInfoKey: windowId]
+            )
             return
         }
         titlebarAccessoryController.toggleNotificationsPopover(animated: animated, anchorView: anchorView)
     }

Then route showNotificationsPopoverFromMenuBar(), dismissNotificationsPopoverIfShown(), and isNotificationsPopoverShown() through the same macOS 26 presenter/observer path instead of the legacy accessory controller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 10168 - 10176, The macOS 26 bridge
only posts a global toggle and drops the originating window, causing desync with
titlebarAccessoryController; change the NotificationCenter bridge used by
toggleNotificationsPopover(animated:anchorView:) to include identifying info
(e.g., the originating NSWindow or a unique window identifier in userInfo) so
the SwiftUI presenter has a deterministic target, and then route
showNotificationsPopoverFromMenuBar(), dismissNotificationsPopoverIfShown(), and
isNotificationsPopoverShown() to use the same macOS 26 notification/presenter
path instead of calling titlebarAccessoryController directly (keep the legacy
controller path for <macOS26). Locate and update the symbols
toggleNotificationsPopover(animated:anchorView:),
showNotificationsPopoverFromMenuBar(), dismissNotificationsPopoverIfShown(), and
isNotificationsPopoverShown() to post/observe and query the unified presenter
notification (using userInfo keys) so all entry points target the same presenter
on macOS 26.

}

Expand Down
Loading