Adopt macOS 26 Liquid Glass design - #2647
rodchristiansen wants to merge 2260 commits into
Conversation
…i#2447) * test: cover printable shift punctuation key equivalents * fix: bypass key equivalents for printable shifted punctuation * Fix terminal test host lifetime
…256color Keep prompt colors when switching local TERM to xterm-256color
…ve (manaflow-ai#2579) * Suppress cmd hover path highlight during selection * Fix cmd-hover links during selection * Strip cmd hover state during selection * Add cmd-click regression UI tests * Resolve escaped-space cmd-click paths * Stabilize cmd-click regression coverage * fix: resolve raw-space cmd-click paths * test: retry cmd-click token geometry capture * test: cover quicklook mismatch cmd-click regression * fix: prefer snapshot path over mismatched quicklook * test: cover viewport-offset cmd-click regression * fix: prefer click-point snapshot over viewport offsets * Add logged path cmd-click regression matrix * Strengthen ls cmd-click cell coverage --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
* Add regression test for lost terminal key routing * Repair focused terminal key routing after responder drift * Address PR review feedback * fix: coerce search focus debug token to String * chore: retrigger PR workflows * fix: address review feedback on focus repair
* Split AppDelegate support code into focused files * Drain privileged installer output before waiting * Clear pipe readers at EOF * Avoid overlapping privileged output reads * Deduplicate resolved Finder directories * Harden shortcut routing edge cases --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Rebased onto current upstream main (resolves PR manaflow-ai#2647 merge conflicts). Summary of the Liquid Glass pass, all gated on #available(macOS 26.0, *): - NavigationSplitView with native glass sidebar and system titlebar - SwiftUI .toolbar with .accessoryBarAction style for bell + new-tab; pane actions (terminal/browser/split) as a ControlGroup - Notifications popover via SwiftUI .popover, scoped to the owning window via a windowId-tagged NotificationCenter broadcast - Unread notification badge on the toolbar bell button - 8pt external padding around terminal detail content; 6pt internal scroll inset so text stays within the safe zone - Leading corners rounded at AppKit portal level when sidebar is visible - SplitViewDividerHider clears NSSplitView divider color - Tab bar collapses to 0 height on macOS 26 (hides for single-tab panes once the matching bonsplit change lands) - System handles app icon appearance on macOS 26 (AppIcon.icon now wired into Resources with folder.iconcomposer.icon type so actool emits Aqua/Dark/Tintable variants) - Legacy titlebar accessory controllers and custom titlebar overlay skipped on macOS 26 - Toggle Sidebar default shortcut changed to ⌃⌘S (system standard) - System sidebar chevron + injected tracking separator stripped Rebase notes for reviewers: - Squashed the original 10-commit PR into one commit while rebasing onto current manaflow-ai/main. No behavior change vs prior tip. - Upstream refactor "Split AppDelegate support code" reconciled by taking the new file layout; Liquid Glass edits move with it. - Submodule vendor/bonsplit pinned at upstream SHA. The single-tab tab bar hiding needs a bonsplit-side change; tracked separately. Known limitations (unchanged from prior tip): - Terminal right-side corner rounding: CAMetalLayer ignores Core Animation clipping; deferred to follow-up. - Light mode sidebar has a subtle glass tint from NavigationSplitView, matching Finder/Mail.
68af6e0 to
850d29c
Compare
Rebased onto current upstream main (resolves PR manaflow-ai#2647 merge conflicts). Summary of the Liquid Glass pass, all gated on #available(macOS 26.0, *): - NavigationSplitView with native glass sidebar and system titlebar - SwiftUI .toolbar with .accessoryBarAction style for bell + new-tab; pane actions (terminal/browser/split) as a ControlGroup - Notifications popover via SwiftUI .popover, scoped to the owning window via a windowId-tagged NotificationCenter broadcast - Unread notification badge on the toolbar bell button - 8pt external padding around terminal detail content; 6pt internal scroll inset so text stays within the safe zone - Leading corners rounded at AppKit portal level when sidebar is visible - SplitViewDividerHider clears NSSplitView divider color - Tab bar collapses to 0 height on macOS 26 (hides for single-tab panes once the matching bonsplit change lands) - System handles app icon appearance on macOS 26 (AppIcon.icon now wired into Resources with folder.iconcomposer.icon type so actool emits Aqua/Dark/Tintable variants) - Legacy titlebar accessory controllers and custom titlebar overlay skipped on macOS 26 - Toggle Sidebar default shortcut changed to ⌃⌘S (system standard) - System sidebar chevron + injected tracking separator stripped Rebase notes for reviewers: - Squashed the original 10-commit PR into one commit while rebasing onto current manaflow-ai/main. No behavior change vs prior tip. - Upstream refactor "Split AppDelegate support code" reconciled by taking the new file layout; Liquid Glass edits move with it. - Submodule vendor/bonsplit pinned at upstream SHA. The single-tab tab bar hiding needs a bonsplit-side change; tracked separately. Known limitations (unchanged from prior tip): - Terminal right-side corner rounding: CAMetalLayer ignores Core Animation clipping; deferred to follow-up. - Light mode sidebar has a subtle glass tint from NavigationSplitView, matching Finder/Mail.
|
Rebased onto current Reconciliation notes:
Local compile ( |
Two linked fixes for the macOS 26 NavigationSplitView toolbar:
1) Keep our own sidebar-toggle button in the leading toolbar position
unconditionally (both states) instead of only when the sidebar is
hidden. The system-injected toggle was the only affordance to hide
the sidebar when it was visible, and we want to strip that everywhere.
2) Strengthen SystemSidebarToggleStripperView so the system toggle does
not leak through when the window is narrow enough to push toolbar
items into the overflow popover:
- Also match items by user-facing label ("Hide Sidebar" / "Show
Sidebar"), since NavigationSplitView re-injects the toggle into
the overflow with an opaque identifier that does not contain
"toggleSidebar".
- Observe NSToolbar.willAddItemNotification and re-strip on every
item insertion, so SwiftUI cannot silently re-add the toggle
after our initial strip.
- Observe NSWindow.didResizeNotification since window resize is
what triggers the re-layout that shoves items into overflow.
Before: narrowing the window revealed a `»` overflow chevron whose
popover contained a system "Hide Sidebar" button even though we had
stripped the same item from the main toolbar at launch.
After: the overflow popover no longer contains any sidebar toggle in
either state, and the leading sidebar button remains available at all
window widths.
There was a problem hiding this comment.
6 issues found across 10 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/GhosttyTerminalView.swift">
<violation number="1" location="Sources/GhosttyTerminalView.swift:9599">
P2: Fixed 6pt inset is not clamped to current bounds, so transient tiny host sizes can produce zero/negative scroll/surface dimensions.</violation>
</file>
<file name="Sources/AppDelegate.swift">
<violation number="1" location="Sources/AppDelegate.swift:8645">
P2: macOS 26 notification toggle can still broadcast globally when no target window is resolved, causing multiple windows to toggle instead of only the intended one.</violation>
<violation number="2" location="Sources/AppDelegate.swift:8651">
P2: macOS 26 notifications popover toggle is SwiftUI-notification driven, but dismiss/isShown still query only the legacy titlebar accessory controller, causing stale state and potential no-op dismiss behavior.</violation>
</file>
<file name="Sources/ContentView.swift">
<violation number="1" location="Sources/ContentView.swift:14750">
P2: Sidebar-toggle stripping depends on hardcoded English toolbar labels, which can fail on localized macOS and leave duplicate sidebar controls.</violation>
<violation number="2" location="Sources/ContentView.swift:14825">
P2: Divider-hiding logic patches every NSSplitView in the window, not just NavigationSplitView, risking unintended UI regressions in unrelated split-based views.</violation>
</file>
<file name="Sources/TerminalWindowPortal.swift">
<violation number="1" location="Sources/TerminalWindowPortal.swift:1507">
P2: Sidebar detection via `targetFrame.origin.x > 20` is not specific to sidebar visibility and can incorrectly round split panes when sidebar is hidden.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
The prior overflow fix matched system-injected toggle items by label
("Hide Sidebar" / "Show Sidebar"), which breaks on localized macOS
builds. Replace the label check with an action-selector check against
toggleSidebar: — the same selector AppKit uses internally for both
NSSplitViewController.toggleSidebar and NavigationSplitView's injected
item, regardless of locale.
Identifier-based matching (items whose itemIdentifier contains
"toggleSidebar" or "splitViewSeparator") remains as the primary path
for items that carry a stable identifier.
Flagged by cursor[bot] and cubic-dev-ai[bot] on PR manaflow-ai#2647.
The prior overflow fix matched system-injected toggle items by label
("Hide Sidebar" / "Show Sidebar"), which breaks on localized macOS
builds. Replace the label check with an action-selector check against
toggleSidebar: — the same selector AppKit uses internally for both
NSSplitViewController.toggleSidebar and NavigationSplitView's injected
item, regardless of locale.
Identifier-based matching (items whose itemIdentifier contains
"toggleSidebar" or "splitViewSeparator") remains as the primary path
for items that carry a stable identifier.
Flagged by cursor[bot] and cubic-dev-ai[bot] on PR manaflow-ai#2647.
Seven follow-ups flagged by cursor[bot] and cubic-dev-ai[bot] on 4/22–4/23:
1. (HIGH) Restore right sidebar panel on macOS 26 — wrap NavigationSplitView
detail with terminalContentWithRightSidebarPanel so File Explorer and
Session Index remain reachable.
2. Bump vendor/bonsplit to cfff8a9 ("Hide tab bar when tabBarHeight is 0")
so the macOS 26 single-tab pane actually collapses the bar instead of
rendering at zero height; switch the submodule URL to rodchristiansen/
bonsplit (same change already on main) since cfff8a9 isn't on a tracked
branch of manaflow-ai/bonsplit. Drop tabTitleFontSize references from
Workspace.swift to match the new bonsplit Appearance API.
3. Clamp the macOS 26 6pt scroll-view inset to max(0, bounds-inset) and
skip it entirely for sub-16pt host bounds so transient tiny layouts
can't produce zero/negative scroll/surface dimensions.
4. AppDelegate.toggleNotificationsPopover early-returns when no
targetWindowId resolves (was broadcasting globally to every observer).
5. dismissNotificationsPopoverIfShown / isNotificationsPopoverShown route
through a per-window state set on macOS 26 so they don't return stale
results from the legacy titlebar accessory controller.
6. SplitViewDividerHider walks up to the nearest enclosing NSSplitView
instead of recursing the whole window.contentView, so unrelated
split-based UI can't be silently mutated.
7. TerminalWindowPortal corner-radius logic asks the enclosing NSSplitView
whether its sidebar subview is collapsed instead of using the
origin.x > 20 frame heuristic; falls back to the heuristic when no
ancestor exists.
Seven follow-ups flagged by cursor[bot] and cubic-dev-ai[bot] on 4/22–4/23:
1. (HIGH) Restore right sidebar panel on macOS 26 — wrap NavigationSplitView
detail with terminalContentWithRightSidebarPanel so File Explorer and
Session Index remain reachable.
2. Bump vendor/bonsplit to cfff8a9 ("Hide tab bar when tabBarHeight is 0")
so the macOS 26 single-tab pane actually collapses the bar instead of
rendering at zero height; switch the submodule URL to rodchristiansen/
bonsplit (same change already on main) since cfff8a9 isn't on a tracked
branch of manaflow-ai/bonsplit. Drop tabTitleFontSize references from
Workspace.swift to match the new bonsplit Appearance API.
3. Clamp the macOS 26 6pt scroll-view inset to max(0, bounds-inset) and
skip it entirely for sub-16pt host bounds so transient tiny layouts
can't produce zero/negative scroll/surface dimensions.
4. AppDelegate.toggleNotificationsPopover early-returns when no
targetWindowId resolves (was broadcasting globally to every observer).
5. dismissNotificationsPopoverIfShown / isNotificationsPopoverShown route
through a per-window state set on macOS 26 so they don't return stale
results from the legacy titlebar accessory controller.
6. SplitViewDividerHider walks up to the nearest enclosing NSSplitView
instead of recursing the whole window.contentView, so unrelated
split-based UI can't be silently mutated.
7. TerminalWindowPortal corner-radius logic asks the enclosing NSSplitView
whether its sidebar subview is collapsed instead of using the
origin.x > 20 frame heuristic; falls back to the heuristic when no
ancestor exists.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c89a423. Configure here.
| [submodule "vendor/bonsplit"] | ||
| path = vendor/bonsplit | ||
| url = https://github.com/manaflow-ai/bonsplit.git | ||
| url = https://github.com/rodchristiansen/bonsplit.git |
There was a problem hiding this comment.
Bonsplit submodule URL changed to personal fork
Medium Severity
The bonsplit submodule URL was changed from the organization repo (manaflow-ai/bonsplit.git) to a personal fork (rodchristiansen/bonsplit.git). Merging this to main would make the project depend on a personal fork rather than the canonical organization repository, creating a bus-factor risk and potential access issues for other contributors.
Reviewed by Cursor Bugbot for commit c89a423. Configure here.
There was a problem hiding this comment.
2 issues found across 7 files (changes from recent commits).
You’re at about 92% of the monthly review limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/Workspace.swift">
<violation number="1" location="Sources/Workspace.swift:6756">
P2: Removing the `surfaceTabBarFontSize` plumbing makes the tab-title font-size setting a no-op for new workspaces and config/theme refreshes.</violation>
</file>
<file name=".gitmodules">
<violation number="1" location=".gitmodules:10">
P2: Point this submodule back to the organization-owned bonsplit repository to avoid relying on a personal fork that can disappear or drift unexpectedly.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| ) | ||
| } | ||
|
|
||
| private static func bonsplitAppearance(from config: GhosttyConfig) -> BonsplitConfiguration.Appearance { |
There was a problem hiding this comment.
P2: Removing the surfaceTabBarFontSize plumbing makes the tab-title font-size setting a no-op for new workspaces and config/theme refreshes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Workspace.swift, line 6756:
<comment>Removing the `surfaceTabBarFontSize` plumbing makes the tab-title font-size setting a no-op for new workspaces and config/theme refreshes.</comment>
<file context>
@@ -6752,8 +6752,7 @@ final class Workspace: Identifiable, ObservableObject {
- backgroundOpacity: config.backgroundOpacity,
- tabTitleFontSize: config.surfaceTabBarFontSize
+ backgroundOpacity: config.backgroundOpacity
)
}
</file context>
| [submodule "vendor/bonsplit"] | ||
| path = vendor/bonsplit | ||
| url = https://github.com/manaflow-ai/bonsplit.git | ||
| url = https://github.com/rodchristiansen/bonsplit.git |
There was a problem hiding this comment.
P2: Point this submodule back to the organization-owned bonsplit repository to avoid relying on a personal fork that can disappear or drift unexpectedly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .gitmodules, line 10:
<comment>Point this submodule back to the organization-owned bonsplit repository to avoid relying on a personal fork that can disappear or drift unexpectedly.</comment>
<file context>
@@ -7,4 +7,4 @@
[submodule "vendor/bonsplit"]
path = vendor/bonsplit
- url = https://github.com/manaflow-ai/bonsplit.git
+ url = https://github.com/rodchristiansen/bonsplit.git
</file context>
| url = https://github.com/rodchristiansen/bonsplit.git | |
| url = https://github.com/manaflow-ai/bonsplit.git |
Two follow-ups after on-device verification of 2248c67: 1. Vertical line at the sidebar boundary returned because the prior ancestor walk in SplitViewDividerHider doesn't find the NavigationSplitView's NSSplitView — the hider is mounted as a `.background`, which sits as a sibling in SwiftUI's hosting hierarchy rather than as a descendant. Walk down from window.contentView and patch only the topmost (first) NSSplitView found, which is reliably the NavigationSplitView. Still respects cubic[bot]'s concern (no recursive patching of every split view in the window). 2. Sidebar-toggle button placement now follows Apple HIG: when the sidebar is shown, the toggle sits inside the sidebar column's leading edge (matching Mail/Notes); when collapsed, it appears in the leading toolbar position. Achieved by removing the custom ToolbarItem(placement: .navigation) toggle and the SystemSidebarToggleStripper — NavigationSplitView's native toggle provides the correct placement automatically. The columnVisibility binding wires the native toggle into our SidebarState.
The previous attempt to rely entirely on NavigationSplitView's auto-injected sidebar toggle backfired: on macOS 26 the system toggle gets pushed into the toolbar overflow popover when the sidebar is collapsed, leaving no visible button on the leading edge. Reinstate the custom ToolbarItem(placement: .navigation) sidebar toggle so there's always a visible leading-edge button, plus SystemSidebarToggleStripper to remove the system duplicate. The stripper's match rules (action == toggleSidebar: or itemIdentifier contains "toggleSidebar"/"splitViewSeparator") only match AppKit-native items; our SwiftUI button uses neither, so it survives.
Mail/Notes pattern (Apple HIG): when the sidebar is shown, the toggle button sits at the top of the sidebar column itself; when the sidebar is hidden, the toggle appears on the leading edge of the toolbar so the user can re-open it. Implementation: - Add a borderless sidebar.left button at the top of the sidebar VStack in the macOS 26 NavigationSplitView. It's only visible when the sidebar is shown (because the whole column is collapsed otherwise). - Make the ToolbarItem(placement: .navigation) toggle conditional on sidebarState.isVisible == false. So exactly one toggle is visible in every state, in the right place, and the bottom-of-stack SystemSidebarToggleStripper continues to drop any system duplicate.
Putting .toolbar on the sidebar view (rather than the NavigationSplitView root) places the leading-navigation item at title-bar y-level *inside* the sidebar column — Apple HIG / Mail / Notes pattern. NavigationSplitView auto-relocates the same item to the leading edge of the detail toolbar when the sidebar is collapsed, so a single ToolbarItem covers both states. The SystemSidebarToggleStripper continues to drop any auto-injected duplicate.
User-observed behavior on macOS 26: the system-injected toggle DOES place itself correctly (inside the sidebar column at title-bar y-level when expanded; leading edge of the detail toolbar when collapsed). The previous SystemSidebarToggleStripper was killing the *good* placement during sidebar drag, leaving only our custom (wrong-position) toggle. Drop the stripper and the custom ToolbarItem entirely; let NavigationSplitView handle sidebar-toggle placement on macOS 26. The columnVisibility binding still routes the system toggle through SidebarState. Reverts both 0d25bbb (system-toggle stripper + custom leading toggle) and 19a63d7 (re-introduction of the stripper after the prior trial of this approach), now that the sidebar-internal trial of 27ea2e3/2eb854bc showed the system toggle was already placing itself in the right spot. If the overflow regression returns when the window is narrowed, we'll solve it with toolbar-item priority instead of stripping.
…e when hidden When the sidebar is shown, NavigationSplitView's auto-injected toggle places itself correctly inside the sidebar column at title-bar y-level. When the sidebar is hidden, the same toggle gets shoved into the toolbar overflow popover instead of the leading edge. Gate both the SystemSidebarToggleStripper and the custom ToolbarItem(placement: .navigation) on `!sidebarState.isVisible`. Result: * Sidebar shown: only the system in-sidebar toggle is visible, in the right place. * Sidebar hidden: stripper kills the overflow duplicate, custom leading toolbar item is the user-visible affordance. Local-only commit (not pushing to PR while iterating).
cdf6eb0 to
ef0ed32
Compare
|
Too many files changed for review (1265 files, 100 file limit). |


Summary
.toolbarfor bell and new-tab buttons with.accessoryBarActionstyleKnown limitations
Test plan
🤖 Generated with Claude Code
Summary by cubic
Adopts macOS 26 Liquid Glass and updates cmux’s current app and CLI baseline. On macOS 26, custom AppKit titlebar/sidebar handling is replaced with native
NavigationSplitView, system titlebar behavior, and SwiftUI toolbar actions; older macOS versions keep the legacy path.App changes
⌃⌘S, and native window dragging.CAMetalLayerdoes not honor parent view clipping.CLI, integrations, and delivery
cmuxtermwith the SwiftcmuxCLI and addscmux omx/cmux omcintegrations.setup-hooksanduninstall-hookssupport for Codex, Cursor, Gemini, Copilot, CodeBuddy, Factory, and Qoder.TERMhandoff, userzshenvopt-outs, and printable shifted-punctuation input fixes.Written for commit ef0ed32. Summary will update on new commits.
Summary by CodeRabbit
New Features
Improvements
Other
Note
Medium Risk
Medium risk because it changes core window/layout behavior and notification popover handling with macOS-version branching (including some AppKit toolbar/view hierarchy manipulation) that could regress multi-window UI interactions.
Overview
On macOS 26+, the main window layout switches to
NavigationSplitViewto get native Liquid Glass sidebar/titlebar behavior, adds an 8pt padded detail area, disables custom titlebar overlays/accessories, and relies on system safe areas/window dragging instead of the previous "immovable window" approach.Notifications UI is reworked for macOS 26: the bell button becomes a SwiftUI toolbar item with a per-window SwiftUI popover, plus new AppDelegate notification broadcasts/state tracking so menu/command invocations toggle/dismiss the correct window’s popover.
Additional macOS 26 polish includes Liquid Glass hover/active effects in the sidebar, hiding bonsplit’s single-tab tab bar, insetting terminal scroll content for rounded-corner safe zones, rounding terminal leading corners based on actual split-view sidebar visibility, and avoiding manual
WindowGlassEffectinsertion so it doesn’t fight system glass. Separately, the default Toggle Sidebar shortcut changes and project wiring/resources are updated (including bundlingAppIcon.iconand updating thevendor/bonsplitsubmodule URL).Reviewed by Cursor Bugbot for commit cdf6eb0. Bugbot is set up for automated code reviews on this repo. Configure here.