Bump version to 1.27.0 (#35)

Fix macOS 14 (Sonoma) compatibility issues:
- Muted traffic lights and toolbar items caused by clipsToBounds default change
- Toolbar buttons disappearing after sidebar toggle (Cmd+B)
- Update check pill not appearing in titlebar
This commit is contained in:
Lawrence Chen 2026-02-11 20:30:12 -08:00 committed by GitHub
parent 0dd3252980
commit eb19e8fa25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 127 additions and 8 deletions

View file

@ -2,6 +2,13 @@
All notable changes to cmux are documented here.
## [1.27.0] - 2026-02-11
### Fixed
- Muted traffic lights and toolbar items on macOS 14 (Sonoma) caused by `clipsToBounds` default change
- Toolbar buttons (sidebar, notifications, new tab) disappearing after toggling sidebar with Cmd+B
- Update check pill not appearing in titlebar on macOS 14 (Sonoma)
## [1.26.0] - 2026-02-11
### Fixed

View file

@ -538,7 +538,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 37;
CURRENT_PROJECT_VERSION = 38;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GENERATE_INFOPLIST_FILE = YES;
@ -554,7 +554,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.26.0;
MARKETING_VERSION = 1.27.0;
OTHER_LDFLAGS = (
"-lc++",
"-framework",
@ -583,7 +583,7 @@
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 37;
CURRENT_PROJECT_VERSION = 38;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GENERATE_INFOPLIST_FILE = YES;
@ -599,7 +599,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.26.0;
MARKETING_VERSION = 1.27.0;
OTHER_LDFLAGS = (
"-lc++",
"-framework",
@ -652,10 +652,10 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 37;
CURRENT_PROJECT_VERSION = 38;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.26.0;
MARKETING_VERSION = 1.27.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.cmux.appuitests;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -669,10 +669,10 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 37;
CURRENT_PROJECT_VERSION = 38;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.26.0;
MARKETING_VERSION = 1.27.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.cmux.appuitests;
PRODUCT_NAME = "$(TARGET_NAME)";

View file

@ -68,6 +68,14 @@ enum WindowGlassEffect {
blurView.autoresizingMask = [.width, .height]
blurView.layer?.zPosition = -1000
// macOS 14 (Sonoma) changed clipsToBounds default from YES to NO.
// Explicitly restore clipping to prevent blur/tint from rendering
// beyond contentView bounds and interfering with titlebar compositing.
if #available(macOS 14, *) {
blurView.clipsToBounds = true
contentView.clipsToBounds = true
}
contentView.addSubview(blurView, positioned: .below, relativeTo: contentView.subviews.first)
// Tint overlay on top of blur, still behind content
@ -77,6 +85,9 @@ enum WindowGlassEffect {
tintOverlay.wantsLayer = true
tintOverlay.layer?.backgroundColor = color.cgColor
tintOverlay.layer?.zPosition = -999
if #available(macOS 14, *) {
tintOverlay.clipsToBounds = true
}
contentView.addSubview(tintOverlay, positioned: .above, relativeTo: blurView)
objc_setAssociatedObject(window, &tintOverlayKey, tintOverlay, .OBJC_ASSOCIATION_RETAIN)
}
@ -114,10 +125,12 @@ enum WindowGlassEffect {
}
final class SidebarState: ObservableObject {
static let didToggleNotification = Notification.Name("SidebarStateDidToggle")
@Published var isVisible: Bool = true
func toggle() {
isVisible.toggle()
NotificationCenter.default.post(name: Self.didToggleNotification, object: nil)
}
}

View file

@ -33,6 +33,11 @@ final class DevBuildAccessoryViewController: NSTitlebarAccessoryViewController {
hostingView.autoresizingMask = [.width, .height]
containerView.addSubview(hostingView)
if #available(macOS 14, *) {
containerView.clipsToBounds = true
hostingView.clipsToBounds = true
}
scheduleSizeUpdate()
}
@ -42,11 +47,17 @@ final class DevBuildAccessoryViewController: NSTitlebarAccessoryViewController {
override func viewDidAppear() {
super.viewDidAppear()
view.isHidden = false
containerView.isHidden = false
hostingView.isHidden = false
scheduleSizeUpdate()
}
override func viewDidLayout() {
super.viewDidLayout()
view.isHidden = false
containerView.isHidden = false
hostingView.isHidden = false
scheduleSizeUpdate()
}
@ -63,6 +74,7 @@ final class DevBuildAccessoryViewController: NSTitlebarAccessoryViewController {
hostingView.invalidateIntrinsicContentSize()
hostingView.layoutSubtreeIfNeeded()
let labelSize = hostingView.fittingSize
guard labelSize.width > 1 && labelSize.height > 1 else { return }
let titlebarHeight = view.window.map { window in
window.frame.height - window.contentLayoutRect.height
} ?? labelSize.height
@ -368,6 +380,13 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
hostingView.autoresizingMask = [.width, .height]
containerView.addSubview(hostingView)
// macOS 14 (Sonoma) changed clipsToBounds default to NO, which can cause
// titlebar accessory views to render incorrectly or disappear during layout.
if #available(macOS 14, *) {
containerView.clipsToBounds = true
hostingView.clipsToBounds = true
}
userDefaultsObserver = NotificationCenter.default.addObserver(
forName: UserDefaults.didChangeNotification,
object: nil,
@ -391,14 +410,24 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
override func viewDidAppear() {
super.viewDidAppear()
ensureVisible()
scheduleSizeUpdate()
}
override func viewDidLayout() {
super.viewDidLayout()
ensureVisible()
scheduleSizeUpdate()
}
/// Sonoma can hide titlebar accessory views during layout transitions.
/// Force visibility on every layout pass.
private func ensureVisible() {
view.isHidden = false
containerView.isHidden = false
hostingView.isHidden = false
}
private func scheduleSizeUpdate() {
guard !pendingSizeUpdate else { return }
pendingSizeUpdate = true
@ -412,6 +441,8 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
hostingView.invalidateIntrinsicContentSize()
hostingView.layoutSubtreeIfNeeded()
let contentSize = hostingView.fittingSize
// Guard against zero-size frames during layout transitions (Sonoma)
guard contentSize.width > 1 && contentSize.height > 1 else { return }
let titlebarHeight = view.window.map { window in
window.frame.height - window.contentLayoutRect.height
} ?? contentSize.height
@ -673,6 +704,11 @@ final class UpdateAccessoryViewController: NSTitlebarAccessoryViewController {
hostingView.autoresizingMask = [.width, .height]
containerView.addSubview(hostingView)
if #available(macOS 14, *) {
containerView.clipsToBounds = true
hostingView.clipsToBounds = true
}
stateCancellable = model.$state
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
@ -688,14 +724,22 @@ final class UpdateAccessoryViewController: NSTitlebarAccessoryViewController {
override func viewDidAppear() {
super.viewDidAppear()
ensureVisible()
scheduleSizeUpdate()
}
override func viewDidLayout() {
super.viewDidLayout()
ensureVisible()
scheduleSizeUpdate()
}
private func ensureVisible() {
view.isHidden = false
containerView.isHidden = false
hostingView.isHidden = false
}
private func scheduleSizeUpdate() {
guard !pendingSizeUpdate else { return }
pendingSizeUpdate = true
@ -709,6 +753,7 @@ final class UpdateAccessoryViewController: NSTitlebarAccessoryViewController {
hostingView.invalidateIntrinsicContentSize()
hostingView.layoutSubtreeIfNeeded()
let pillSize = hostingView.fittingSize
guard pillSize.width > 1 && pillSize.height > 1 else { return }
let titlebarHeight = view.window.map { window in
window.frame.height - window.contentLayoutRect.height
} ?? pillSize.height
@ -750,6 +795,7 @@ final class UpdateTitlebarAccessoryController {
attachToExistingWindows()
installObservers()
installStateObserver()
installSidebarToggleObserver()
}
func attach(to window: NSWindow) {
@ -830,6 +876,52 @@ final class UpdateTitlebarAccessoryController {
window.identifier?.rawValue == "cmux.main"
}
/// After sidebar toggle on Sonoma, titlebar accessories can disappear. Re-add if needed.
private func installSidebarToggleObserver() {
let center = NotificationCenter.default
observers.append(center.addObserver(
forName: SidebarState.didToggleNotification,
object: nil,
queue: .main
) { [weak self] _ in
// Delay slightly to let SwiftUI layout settle before revalidating
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self?.revalidateAllAccessories()
}
})
}
private func revalidateAllAccessories() {
guard let updateViewModel else { return }
for window in attachedWindows.allObjects {
// Re-add controls if they were removed during layout
if !window.titlebarAccessoryViewControllers.contains(where: { $0.view.identifier == controlsIdentifier }) {
let controls = TitlebarControlsAccessoryViewController(
notificationStore: TerminalNotificationStore.shared
)
controls.layoutAttribute = .left
controls.view.identifier = controlsIdentifier
window.addTitlebarAccessoryViewController(controls)
controlsControllers.add(controls)
}
// Re-add update accessory if it was removed and state is not idle
let isIdle = (updateViewModel.overrideState ?? updateViewModel.state).isIdle
if !isIdle && !window.titlebarAccessoryViewControllers.contains(where: { $0.view.identifier == updateIdentifier }) {
let accessory = UpdateAccessoryViewController(model: updateViewModel)
accessory.layoutAttribute = .right
accessory.view.identifier = updateIdentifier
window.addTitlebarAccessoryViewController(accessory)
}
// Ensure all accessories are visible and properly sized
for controller in window.titlebarAccessoryViewControllers {
controller.view.isHidden = false
controller.view.needsLayout = true
}
}
}
private func installStateObserver() {
guard let updateViewModel else { return }
stateCancellable = Publishers.CombineLatest(updateViewModel.$state, updateViewModel.$overrideState)

View file

@ -5,6 +5,13 @@ description: Release notes and version history for cmux
All notable changes to cmux are documented here.
## [1.27.0] - 2026-02-11
### Fixed
- Muted traffic lights and toolbar items on macOS 14 (Sonoma) caused by `clipsToBounds` default change
- Toolbar buttons (sidebar, notifications, new tab) disappearing after toggling sidebar with Cmd+B
- Update check pill not appearing in titlebar on macOS 14 (Sonoma)
## [1.26.0] - 2026-02-11
### Fixed