Skip to content

Adopt macOS 26 Liquid Glass design#2647

Open
rodchristiansen wants to merge 10 commits intomanaflow-ai:mainfrom
rodchristiansen:liquid-glass-design
Open

Adopt macOS 26 Liquid Glass design#2647
rodchristiansen wants to merge 10 commits intomanaflow-ai:mainfrom
rodchristiansen:liquid-glass-design

Conversation

@rodchristiansen
Copy link
Copy Markdown

@rodchristiansen rodchristiansen commented Apr 6, 2026

Summary

  • NavigationSplitView with native glass sidebar and system titlebar on macOS 26
  • SwiftUI .toolbar for bell and new-tab buttons with .accessoryBarAction style
  • 8pt external padding around terminal detail content for Liquid Glass spacing
  • 6pt internal terminal content inset keeps text within safe zone
  • Leading corners (top-left, bottom-left) rounded at AppKit portal level when sidebar visible
  • Bonsplit tab bar hidden for single-tab panes on macOS 26
  • Old titlebar accessory controllers and custom titlebar overlay skipped on macOS 26
  • App icon auto-set skipped on macOS 26 (system handles dark variants)

Known limitations

  • Terminal corner rounding (right side): CAMetalLayer ignores Core Animation clipping (cornerRadius, masksToBounds, layer.mask on parent views). Left corners work because the sidebar glass provides contrast. Full corner rounding deferred to follow-up.
  • Light mode sidebar tint: NavigationSplitView's glass material adds a subtle gray tint in light mode. This matches native apps (Finder, Mail) and is the expected system behavior.

Test plan

  • Verify glass sidebar renders correctly in light and dark mode
  • Verify toolbar buttons (bell, +) appear and function
  • Verify 8pt padding around terminal on all sides
  • Verify terminal text has internal padding (doesn't touch edges)
  • Verify left corners are rounded when sidebar is visible
  • Toggle sidebar visibility — padding and corners adapt
  • Test fullscreen transitions
  • Test split panes and workspace switching
  • Test drag/drop reordering in sidebar

🤖 Generated with Claude Code


Summary by cubic

Adopts macOS 26 Liquid Glass with a native NavigationSplitView sidebar, system titlebar, and a SwiftUI toolbar. Restores the right sidebar and uses the system sidebar toggle when visible, with a custom leading toolbar toggle when hidden to avoid toolbar-overflow issues.

  • New Features

    • macOS 26 layout: native glass sidebar/titlebar with system safe areas; persists sidebar width; hides only the topmost split divider; restores the right sidebar (File Explorer/Session Index); sidebar toggle inside the sidebar when visible, and a custom leading toolbar toggle when hidden.
    • Toolbar and notifications: pane actions (new terminal/browser/split), notifications via SwiftUI .popover with an unread badge and per-window scoping, and new tab; start the accessory controller on all versions for popover support but skip attaching legacy accessories on macOS 26.
    • Terminal/window polish: 8pt outer padding and a clamped 6pt leading/top scroll inset on macOS 26; round leading corners when a sidebar is present; native window dragging/titlebar on macOS 26; hide single‑tab bonsplit tab bar on macOS 26; change Toggle Sidebar to ⌃⌘S; subtle glass hovers on active tabs and sidebar footer buttons; include AppIcon.icon in Resources and skip custom app‑icon switching on macOS 26.
  • Bug Fixes

    • Sidebar toggle placement: when hidden, mount a custom leading toggle and strip the system-injected duplicate so it doesn’t get trapped in the toolbar overflow; when visible, trust the in‑sidebar system toggle for correct HIG placement.
    • Notifications: popover is fully window‑scoped on macOS 26 (targeted broadcasts; accurate dismiss/isShown state).
    • Robustness: hide only the topmost NSSplitView divider; base corner rounding on actual sidebar visibility via NSSplitView; clamp terminal insets to avoid invalid frames during animated resizes.

Written for commit cdf6eb0. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added toolbar items for sidebar, notifications, and creating a new workspace.
    • Notifications can appear in a dedicated popover and via system notification on newer macOS.
  • Improvements

    • Modern split-view navigation with constrained sidebar widths and hidden dividers on newer macOS.
    • Updated titlebar/window appearance and enabled window movability on newer macOS.
    • Reduced terminal top padding, refined corner rounding and scroll insets, removed sidebar scrim.
    • Subtle glass hover effects for tabs and footer buttons.
  • Other

    • Default sidebar shortcut changed; app icon resource explicitly included.

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 NavigationSplitView to 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 WindowGlassEffect insertion so it doesn’t fight system glass. Separately, the default Toggle Sidebar shortcut changes and project wiring/resources are updated (including bundling AppIcon.icon and updating the vendor/bonsplit submodule URL).

Reviewed by Cursor Bugbot for commit cdf6eb0. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI review requested due to automatic review settings April 6, 2026 21:14
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 6, 2026

@rodchristiansen is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented Apr 6, 2026

This review could not be run because your cubic account has exceeded the monthly review limit. If you need help restoring access, please contact contact@cubic.dev.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

macOS 26+ gated UI and window behavior changes: safe-area forwarding, NavigationSplitView sidebar, new toolbar items and notification popover path, titlebar accessory attachment gated, terminal scroll/layout and hosted-layer corner rounding adjusted, bonsplit submodule bumped, and AppIcon added to project resources.

Changes

Cohort / File(s) Summary
Window & Titlebar
Sources/AppDelegate.swift, Sources/WindowToolbarController.swift
Gated safe-area forwarding and titlebar/window properties for macOS 26+ (forward safeArea, titlebarAppearsTransparent=false, isMovable=true, titlebarSeparatorStyle); toolbar style -> .unified and three new toolbar items (sidebar, notifications, new workspace) with new handlers.
Content layout & Sidebar
Sources/ContentView.swift
macOS 26+ path switches to NavigationSplitView with bound columnVisibility, constrained widths, new toolbar layout, removed legacy titlebar overlay/scrim/backdrop, added SplitViewDividerHider, SystemSidebarToggleStripper, and glass hover modifiers; older macOS path preserved.
Terminal hosting & Scrolling
Sources/GhosttyTerminalView.swift, Sources/TerminalWindowPortal.swift
Scroll-view frame calc adjusted for macOS 26+ (6pt inset on leading/top); portal geometry update now sets hosted layer cornerRadius and maskedCorners when sidebar is to the left, applied conditionally during geometry sync.
App lifecycle & Appearance
Sources/cmuxApp.swift, Sources/Workspace.swift
Extracted mainWindowContent view; AppIconSettings.applyIcon early-guards macOS 26+ to skip icon overrides; Workspace appearance may hide tab bar / split buttons on macOS 26+.
Accessory / Popover & Visibility State
Sources/Update/UpdateTitlebarAccessory.swift, Sources/ContentView.swift
NotificationsPopoverView visibility widened; ContentView adds isNotificationsPopoverPresented and responds to a macOS 26+ notification posted by AppDelegate to present SwiftUI popover; legacy accessory toggle retained for older macOS.
Toolbar accessory startup
Sources/AppDelegate.swift, Sources/Update/UpdateTitlebarAccessory.swift
titlebarAccessoryController.start() now invoked unconditionally; legacy attachment skipped on macOS 26+ via early return in attach path.
Vendored dependency
vendor/bonsplit
Updated submodule pointer to a newer commit SHA (vendor bump).
Project config
GhosttyTabs.xcodeproj/project.pbxproj
Added AppIcon.icon as a resources build file and set lastKnownFileType to folder.iconcomposer.icon.
Keyboard shortcuts
Sources/KeyboardShortcutSettings.swift
Changed default toggleSidebar shortcut from Cmd‑B to Ctrl‑⌘‑S ("s", command+control).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Toolbar as NSToolbar
    participant AppDelegate
    participant Content as ContentView
    participant Accessory as TitlebarAccessory

    User->>Toolbar: Click "Notifications" item
    Toolbar->>AppDelegate: toolbar action toggleNotificationsPopover
    alt macOS >= 26
        AppDelegate->>AppDelegate: post Notification.Name("cmux.toggleNotificationsPopover")
        AppDelegate-->>Content: Notification delivered
        Content->>Content: set isNotificationsPopoverPresented = true
        Content->>User: present SwiftUI popover
    else macOS < 26
        AppDelegate->>Accessory: titlebarAccessoryController.toggleNotificationsPopover(animated:)
        Accessory->>User: toggle accessory popover
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
I hopped through safe areas and split-view light,
Tucked corners neat and tuned the toolbar bright,
A popover ping, a tiny glassy hop,
I nudged the sidebar and then I stopped,
macOS 26 — I twitched my whiskers with delight ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Adopt macOS 26 Liquid Glass design' clearly and concisely summarizes the main objective of the PR, which is implementing the Liquid Glass UI for macOS 26.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering what changed, why, and includes a detailed test plan with known limitations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 6, 2026

Greptile Summary

This PR adopts macOS 26 Liquid Glass by switching to NavigationSplitView, adding native SwiftUI .toolbar buttons, rounding terminal leading corners, and hiding the bonsplit tab bar — but two P1 regressions need fixing before merge.

  • Bell button broken on macOS 26: attachUpdateAccessory(to:) returns early, so UpdateTitlebarAccessoryController never starts; toggleNotificationsPopover guards on !controllers.isEmpty and silently returns. Clicking the bell does nothing.
  • Tab bar unconditionally hidden: tabBarHeight = 0 is set for all panes regardless of tab count, not just single-tab panes as described, removing in-pane tab switching in split workspaces.

Confidence Score: 4/5

Not safe to merge — two P1 regressions ship broken functionality to macOS 26 users.

The bell-button no-op and unconditional tab-bar hiding are both present defects on the changed code path requiring fixes before merge.

Sources/AppDelegate.swift (broken notifications popover) and Sources/Workspace.swift (unconditional tab bar hide)

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Skips titlebarAccessoryController.start() on macOS 26, making the toolbar bell button a silent no-op via empty controlsControllers
Sources/Workspace.swift Unconditionally sets tabBarHeight=0 on macOS 26 for all panes, breaking multi-tab pane navigation in split workspaces
Sources/ContentView.swift Adopts NavigationSplitView for glass sidebar; sidebar width changes from native divider drag not persisted back to sidebarState
Sources/GhosttyTerminalView.swift Adds 6pt symmetric inset on macOS 26 but only leading corners are rounded, unnecessarily shrinking right/bottom terminal area
Sources/TerminalWindowPortal.swift Rounds leading corners when targetFrame.origin.x > 20; magic-number heuristic for sidebar presence is fragile
Sources/WindowToolbarController.swift Adds sidebar toggle, notifications, and new-tab toolbar items on macOS 26 with unified toolbar style; looks correct
Sources/cmuxApp.swift Skips custom app icon on macOS 26 and uses system safe area in MainWindowHostingView; no issues

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[macOS 26 toolbar bell click] --> B[AppDelegate.toggleNotificationsPopover]
    B --> C[titlebarAccessoryController.toggleNotificationsPopover]
    C --> D{controlsControllers.isEmpty?}
    D -- yes macOS 26 --> E[return NO-OP]
    D -- no pre-26 --> F[show popover]
    G[App launch macOS 26] --> H[skip titlebarAccessoryController.start]
    H --> J[controlsControllers stays empty]
    K[Window created macOS 26] --> L[attachUpdateAccessory]
    L --> M{macOS 26?}
    M -- yes --> N[return early no start/attach]
    M -- no --> O[start + attach controller]
Loading

Reviews (1): Last reviewed commit: "Adopt macOS 26 Liquid Glass design" | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift
Comment on lines 10152 to 10160
func attachUpdateAccessory(to window: NSWindow) {
if #available(macOS 26.0, *) {
// On macOS 26, toolbar buttons are native SwiftUI .toolbar items
// in the NavigationSplitView. Skip the old titlebar accessory controllers.
return
}
titlebarAccessoryController.start()
titlebarAccessoryController.attach(to: window)
}
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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

titlebarAccessoryController.start() is already called unconditionally at app launch (AppDelegate.swift:2605), so controlsControllers is populated on macOS 26 too. On 26, the SwiftUI toolbar's bell button toggles local @State directly, and menu/shortcut paths go through toggleNotificationsPopover → NotificationCenter broadcast. The broadcast is now window-scoped in afb1af9.

Comment thread Sources/Workspace.swift
Comment on lines +6781 to +6788
let hideTabBar: Bool
if #available(macOS 26.0, *) {
hideTabBar = true
} else {
hideTabBar = false
}
return BonsplitConfiguration.Appearance(
tabBarHeight: hideTabBar ? 0 : 33,
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 Tab bar unconditionally hidden for all panes, not just single-tab panes

Setting hideTabBar = true on macOS 26 sets tabBarHeight = 0 for every pane, regardless of how many tabs it contains. The PR description says "single-tab panes," but no tab count is checked here. In a split workspace with multiple tabs per pane, all in-pane tab switching UI disappears on macOS 26.

If the goal is to hide the bar only for single-tab panes, the appearance must be updated reactively based on bonsplitController.tabs(inPane:).count rather than as a static launch-time configuration.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Resolved upstream by companion PR manaflow-ai/bonsplit#95 ("Hide tab bar when tabBarHeight is 0, show only when 2+ tabs"). Once that merges and the bonsplit submodule pointer bumps, tabBarHeight = 0 only hides the chrome on single-tab panes; multi-tab panes still show the bar.

Comment thread Sources/ContentView.swift
Comment on lines +2884 to +2924
NavigationSplitView(columnVisibility: Binding(
get: { sidebarState.isVisible ? .all : .detailOnly },
set: { newValue in
let shouldShow = (newValue != .detailOnly)
if shouldShow != sidebarState.isVisible {
_ = sidebarState.toggle()
}
}
)) {
sidebarView
.navigationSplitViewColumnWidth(min: 120, ideal: sidebarWidth, max: 400)
} detail: {
terminalContentWithSidebarDropOverlay
.padding(8)
}
.navigationSplitViewStyle(.prominentDetail)
.background(SplitViewDividerHider())
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
Button {
_ = AppDelegate.shared?.toggleNotificationsPopover(animated: true)
} label: {
Image(systemName: "bell")
}
.buttonStyle(.accessoryBarAction)
.accessibilityIdentifier("toolbar.notifications")

Button {
if let appDelegate = AppDelegate.shared {
if appDelegate.addWorkspaceInPreferredMainWindow(debugSource: "toolbar.newTab") == nil {
appDelegate.openNewMainWindow(nil)
}
}
} label: {
Image(systemName: "plus")
}
.buttonStyle(.accessoryBarAction)
.accessibilityIdentifier("toolbar.newTab")
}
}
)
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.

P2 Sidebar width not persisted when user drags the NavigationSplitView divider

navigationSplitViewColumnWidth(min: 120, ideal: sidebarWidth, max: 400) provides an initial hint, but drag events from the native divider are not fed back into sidebarWidth or sidebarState.persistedWidth. Every sidebar toggle or app restart snaps back to the last value written by the old custom-resizer path, ignoring any width the user set via the system divider on macOS 26.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Already addressed — ContentView.swift:2900-2909 wraps the sidebar column in a GeometryReader that writes back to both sidebarWidth and sidebarState.persistedWidth whenever the rendered width diverges by more than 1pt, so native divider drags persist.

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment on lines +9120 to +9127
let contentInset: CGFloat
if #available(macOS 26.0, *) {
contentInset = 6
} else {
contentInset = 0
}
let scrollFrame = contentInset > 0 ? bounds.insetBy(dx: contentInset, dy: contentInset) : bounds
_ = setFrameIfNeeded(scrollView, to: scrollFrame)
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.

P2 Symmetric inset shrinks right/bottom unnecessarily

bounds.insetBy(dx: contentInset, dy: contentInset) applies the 6 pt padding on all four sides. Only the leading corners (top-left, bottom-left) are rounded; the right and bottom edges are flat. The right-side and bottom 6 pt waste terminal real estate with no visual justification. Consider applying inset only on the leading and top edges where rounding occurs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not symmetric — the code uses x: origin.x + inset, y: origin.y, width: W - inset, height: H - inset, which insets the leading edge (x+6) and the top edge (height-6 with unchanged y in AppKit's bottom-left origin). Right and bottom are unchanged. Comment and behavior match.

Comment on lines +1507 to +1516
if #available(macOS 26.0, *) {
let hasSidebarToLeft = targetFrame.origin.x > 20
let desiredRadius: CGFloat = hasSidebarToLeft ? 16 : 0
if hostedView.layer?.cornerRadius != desiredRadius {
hostedView.layer?.cornerRadius = desiredRadius
hostedView.layer?.maskedCorners = hasSidebarToLeft
? [.layerMinXMinYCorner, .layerMinXMaxYCorner]
: []
}
}
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.

P2 Magic-number heuristic for sidebar detection is fragile

let hasSidebarToLeft = targetFrame.origin.x > 20 infers sidebar presence from the terminal frame's x-origin. A window positioned near x = 0 with no sidebar, or a sidebar narrower than 20 pt during animation, would produce incorrect rounding. Deriving this directly from sidebarState.isVisible would be more robust and self-documenting.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Deferring — the inline comment captures the tradeoff: this AppKit view can't reach SidebarState directly, and sidebar minimum width is 120pt so x > 20 is robust for the current layout. If we refactor to bridge SidebarState into the portal layer we'll tighten this.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the macOS UI path to align with macOS 26 “Liquid Glass” conventions by switching to system-native sidebar/titlebar behaviors and adjusting terminal spacing/clipping to match the new visual language.

Changes:

  • Adds a macOS 26-specific NavigationSplitView layout with SwiftUI toolbar actions and updated window/titlebar configuration.
  • Adjusts terminal presentation (external padding, internal content inset, leading-corner rounding) for Liquid Glass spacing/shape.
  • Skips legacy titlebar accessory / custom glass paths and icon override logic on macOS 26; hides Bonsplit tab bar on macOS 26.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Sources/Workspace.swift macOS 26-specific Bonsplit appearance change to hide tab bar.
Sources/WindowToolbarController.swift Adds macOS 26 toolbar style + additional toolbar items/actions.
Sources/TerminalWindowPortal.swift Adds macOS 26 leading-corner rounding logic for hosted terminal views.
Sources/GhosttyTerminalView.swift Insets terminal scroll view on macOS 26 to create internal safe padding.
Sources/ContentView.swift Introduces macOS 26 NavigationSplitView path, toolbar buttons, window/titlebar behavior changes, sidebar background changes, and new glass-effect modifiers/utilities.
Sources/cmuxApp.swift Refactors main window content builder; skips custom app icon override behavior on macOS 26.
Sources/AppDelegate.swift Enables system safe area on macOS 26 and skips legacy titlebar accessory startup/attachment on macOS 26.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/ContentView.swift
.padding(.top, effectiveTitlebarPadding)
.padding(.top, {
if #available(macOS 26.0, *) {
return 0 // Native glass titlebar handles spacing via safe area
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

On macOS 26 this sets terminalContent top padding to 0 assuming the system safe area will keep content below the titlebar, but ContentView later applies .ignoresSafeArea() to the root view. With safe areas ignored, the terminal content can end up under the titlebar and reintroduce the drag/interaction issues this padding previously avoided. Consider making the safe-area ignoring conditional (e.g., only for the sidebar/background) or keep an appropriate top inset for the detail content on macOS 26.

Suggested change
return 0 // Native glass titlebar handles spacing via safe area
// Keep the terminal content below the titlebar even if an ancestor view
// ignores safe areas; relying on the system safe area here can place
// interactive content under the titlebar.
return effectiveTitlebarPadding

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

On macOS 26 the sidebar sits inside NavigationSplitView, which supplies safe-area insets to its columns; the root-level .ignoresSafeArea() only applies to the custom-titlebar overlay branch, not to the NavigationSplitView path. Not reproducing text under the titlebar during 26 testing, but will keep an eye on it.

Comment thread Sources/ContentView.swift Outdated
Comment on lines +2906 to +2921
Image(systemName: "bell")
}
.buttonStyle(.accessoryBarAction)
.accessibilityIdentifier("toolbar.notifications")

Button {
if let appDelegate = AppDelegate.shared {
if appDelegate.addWorkspaceInPreferredMainWindow(debugSource: "toolbar.newTab") == nil {
appDelegate.openNewMainWindow(nil)
}
}
} label: {
Image(systemName: "plus")
}
.buttonStyle(.accessoryBarAction)
.accessibilityIdentifier("toolbar.newTab")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

.buttonStyle(.accessoryBarAction) is a macOS 26 SwiftUI API; wrapping it only in if #available(macOS 26.0, *) is not sufficient for builds using older SDKs/toolchains (this repo’s Package.swift is swift-tools-version 5.9). To preserve backward build compatibility, gate these calls with a compiler/SDK availability check (similar to the #if compiler(>=6.2) guards used elsewhere) or provide a fallback button style.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Deferring — cmux's Xcode toolchain targets the macOS 26 SDK, so #available(macOS 26.0, *) is sufficient at both runtime and build time. #if compiler(>=6.2) would only matter if we supported builds on older Xcodes, which we don't currently.

Comment thread Sources/ContentView.swift Outdated
Comment on lines +15467 to +15468
// Override the divider color to clear
splitView.setValue(NSColor.clear, forKey: "dividerColor")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

splitView.setValue(NSColor.clear, forKey: "dividerColor") relies on KVC to mutate an AppKit property and can raise an Objective‑C exception at runtime if the key isn’t settable on some OS versions. Prefer a supported API (e.g., setting a public dividerColor if writable) or guard the KVC path with a selector/responds(to:) check and fail safely.

Suggested change
// Override the divider color to clear
splitView.setValue(NSColor.clear, forKey: "dividerColor")
// Override the divider color to clear when the Objective-C setter exists.
let setDividerColorSelector = NSSelectorFromString("setDividerColor:")
if splitView.responds(to: setDividerColorSelector) {
splitView.perform(setDividerColorSelector, with: NSColor.clear)
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Already addressed — the call uses responds(to:) guarded perform(_:with:), not setValue(_:forKey:) (ContentView.swift:15607-15610). No ObjC exception possible if the selector isn't there.

Comment thread Sources/ContentView.swift Outdated
Comment on lines +15430 to +15446
/// Finds NSSplitView(s) inside NavigationSplitView and hides dividers
/// by installing a custom delegate that draws nothing.
@available(macOS 26.0, *)
private struct SplitViewDividerHider: NSViewRepresentable {
func makeNSView(context: Context) -> SplitViewDividerHiderView {
SplitViewDividerHiderView()
}

func updateNSView(_ nsView: SplitViewDividerHiderView, context: Context) {
nsView.scheduleHide()
}
}

@available(macOS 26.0, *)
final class SplitViewDividerHiderView: NSView {
private var installed = false

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The SplitViewDividerHider docs say it hides dividers “by installing a custom delegate”, but the implementation only walks subviews and tweaks properties (no delegate is installed). Also installed is declared but unused, suggesting an intended one-time install that isn’t implemented. Please update the comment to match behavior and either remove installed or use it to prevent repeated recursive patching on every SwiftUI update.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fair — will tighten the docstring in a follow-up. The walk-and-patch approach is the one in use; the installed flag and delegate-install language are stale from an earlier iteration.

Comment thread Sources/ContentView.swift
Comment on lines +3493 to 3507
if #available(macOS 26.0, *) {
// On macOS 26 without fullSizeContentView, the system titlebar
// handles drag natively.
window.isMovable = true
window.isMovableByWindowBackground = false
} else {
// Keep window immovable; the sidebar's WindowDragHandleView handles
// drag-to-move via performDrag with temporary movable override.
// isMovableByWindowBackground=true breaks tab reordering, and
// isMovable=true blocks clicks on sidebar buttons in minimal mode.
window.isMovableByWindowBackground = false
window.isMovable = false
}
window.styleMask.insert(.fullSizeContentView)

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This comment says “On macOS 26 without fullSizeContentView, the system titlebar handles drag natively”, but the code unconditionally inserts .fullSizeContentView right after. Either make the style-mask change conditional for macOS 26 or update the comment, since it currently contradicts the actual behavior and makes debugging titlebar/safe-area interactions harder.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Will clean up the misleading comment in a follow-up. The code path is correct: .fullSizeContentView stays inserted so macOS 26's native glass titlebar draws in-window, and drag behavior works because we keep titlebarAppearsTransparent aligned with the style.

Comment thread Sources/ContentView.swift Outdated
Comment on lines +15415 to +15429
/// Fills the background with the terminal's current background color,
/// staying in sync with theme changes. Used behind NavigationSplitView
/// so the sidebar's rounded corners blend seamlessly with the window.
@available(macOS 26.0, *)
private struct TerminalBackgroundFill: View {
@State private var bgColor = Color(nsColor: GhosttyBackgroundTheme.currentColor())

var body: some View {
bgColor
.onReceive(NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)) { _ in
bgColor = Color(nsColor: GhosttyBackgroundTheme.currentColor())
}
}
}

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

TerminalBackgroundFill is documented as “Used behind NavigationSplitView”, but it isn’t referenced anywhere in ContentView (search only finds its definition). If it’s no longer needed, consider removing it; if it is needed to blend the sidebar corners, wire it into the macOS 26 NavigationSplitView branch so the code matches the comment.

Suggested change
/// Fills the background with the terminal's current background color,
/// staying in sync with theme changes. Used behind NavigationSplitView
/// so the sidebar's rounded corners blend seamlessly with the window.
@available(macOS 26.0, *)
private struct TerminalBackgroundFill: View {
@State private var bgColor = Color(nsColor: GhosttyBackgroundTheme.currentColor())
var body: some View {
bgColor
.onReceive(NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)) { _ in
bgColor = Color(nsColor: GhosttyBackgroundTheme.currentColor())
}
}
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Already addressed — TerminalBackgroundFill is removed from the file.

Comment thread Sources/Workspace.swift
Comment on lines +6781 to 6789
let hideTabBar: Bool
if #available(macOS 26.0, *) {
hideTabBar = true
} else {
hideTabBar = false
}
return BonsplitConfiguration.Appearance(
tabBarHeight: hideTabBar ? 0 : 33,
splitButtonTooltips: Self.currentSplitButtonTooltips(),
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

On macOS 26 hideTabBar is always set to true, which forces tabBarHeight to 0 for all panes. The PR description mentions hiding the tab bar only for single-tab panes; if multi-tab panes should still show a tab bar, this needs conditional logic based on actual tab count/state rather than a blanket OS check.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same resolution as greptile's P1 — companion bonsplit PR manaflow-ai/bonsplit#95 makes the tab bar appear only on panes with 2+ tabs.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
Sources/TerminalWindowPortal.swift (1)

1504-1516: Corner radius updates may animate unexpectedly during geometry sync.

The surrounding frame/bounds updates are wrapped in a CATransaction with disabled actions (lines 1489–1499), but the corner radius changes are applied after the transaction commits. This could cause implicit Core Animation transitions when toggling sidebar visibility, potentially resulting in visual flicker during rapid split/resize operations.

Consider wrapping the corner radius update in its own transaction with disabled actions, or moving it inside the existing transaction block:

🔧 Suggested fix
             CATransaction.commit()
             if geometryChanged {
                 hostedView.reconcileGeometryNow()
                 hostedView.refreshSurfaceNow(reason: "portal.frameChange")
             }

             // On macOS 26, round the terminal's leading corners when a sidebar
             // is visible to its left, matching the NavigationSplitView glass shape.
             if `#available`(macOS 26.0, *) {
                 let hasSidebarToLeft = targetFrame.origin.x > 20
                 let desiredRadius: CGFloat = hasSidebarToLeft ? 16 : 0
                 if hostedView.layer?.cornerRadius != desiredRadius {
+                    CATransaction.begin()
+                    CATransaction.setDisableActions(true)
                     hostedView.layer?.cornerRadius = desiredRadius
                     hostedView.layer?.maskedCorners = hasSidebarToLeft
                         ? [.layerMinXMinYCorner, .layerMinXMaxYCorner]
                         : []
+                    CATransaction.commit()
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TerminalWindowPortal.swift` around lines 1504 - 1516, The corner
radius and maskedCorners updates on hostedView.layer (the block checking
hasSidebarToLeft and setting hostedView.layer?.cornerRadius /
hostedView.layer?.maskedCorners) must be performed inside a CATransaction with
actions disabled (or moved into the existing CATransaction that updates
frame/bounds) to prevent implicit animations during geometry sync; wrap the
radius/maskedCorners assignment in a
CATransaction.begin()/setDisableActions(true)/commit() pair (or relocate those
lines into the existing disabled-action transaction) so changes to cornerRadius
and maskedCorners aren’t implicitly animated.
Sources/WindowToolbarController.swift (1)

230-236: Remove unnecessary _ = assignments.

Both toggle() and toggleNotificationsPopover(animated:) return Void, so the discard assignments are unnecessary.

✨ Suggested cleanup
 `@objc` private func toggleSidebarAction() {
-    _ = AppDelegate.shared?.sidebarState?.toggle()
+    AppDelegate.shared?.sidebarState?.toggle()
 }

 `@objc` private func toggleNotificationsAction() {
-    _ = AppDelegate.shared?.toggleNotificationsPopover(animated: true)
+    AppDelegate.shared?.toggleNotificationsPopover(animated: true)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/WindowToolbarController.swift` around lines 230 - 236, The discard
assignments are unnecessary: remove the `_ =` when calling
AppDelegate.shared?.sidebarState?.toggle() inside toggleSidebarAction() and when
calling AppDelegate.shared?.toggleNotificationsPopover(animated: true) inside
toggleNotificationsAction() so each method is invoked directly (i.e., call
sidebarState?.toggle() and toggleNotificationsPopover(animated: true) without
assigning their Void results).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 2601-2605: The notifications popover is never initialized on macOS
26 because titlebarAccessoryController.start() is skipped; call
titlebarAccessoryController.start() for macOS 26 so
UpdateTitlebarAccessoryController populates controlsControllers and the popover
helpers (toggleNotificationsPopover / showNotificationsPopover /
dismissNotificationsPopoverIfShown) work; specifically, remove or modify the
availability check around titlebarAccessoryController.start() in AppDelegate so
start() always runs (or add a macOS26-specific branch that calls start()), and
ensure Start/UpdateTitlebarAccessoryController safely no-ops if
platform-specific internals differ so
WindowToolbarController.toggleNotificationsAction ->
AppDelegate.toggleNotificationsPopover() works on macOS 26.

In `@Sources/ContentView.swift`:
- Around line 2901-2922: The toolbar's image-only Buttons (the bell and plus in
the ToolbarItemGroup) lack localized action names for VoiceOver; replace each
Button's label Image block with a localized Label using String(localized:
"toolbar.notifications", defaultValue: "Notifications") and String(localized:
"toolbar.newTab", defaultValue: "New Tab") (or similar keys), then apply
.labelStyle(.iconOnly) to keep the icon-only appearance; ensure the same
accessibilityIdentifier values ("toolbar.notifications" and "toolbar.newTab")
remain and add corresponding keys to Resources/Localizable.xcstrings with
English and Japanese translations.
- Around line 2884-2895: The native NavigationSplitView configuration only
writes the preferred width into sidebarWidth (via
navigationSplitViewColumnWidth) but never updates
sidebarWidth/sidebarState.persistedWidth when the user drags the native divider,
causing the custom resizer monitor (which reads
sidebarWidth/sidebarState.persistedWidth) to drift; fix this by observing the
actual rendered sidebar width (e.g., with a GeometryReader or .background
preference) inside the sidebarView and, when its width changes beyond a small
threshold, write the new value back into sidebarWidth and
sidebarState.persistedWidth (and persist via sidebarState.save/persist method if
one exists), ensuring updates are debounced/throttled to avoid tight update
loops and only performed when shouldShow/sidebarState.isVisible matches the
split state.
- Around line 15458-15472: The current approach in
hideDividers()/patchSplitViews(in:) uses KVC to set a read-only NSSplitView
property and indiscriminately mutates every split view in the window; instead,
create a focused custom NSSplitView subclass (e.g., TransparentDividerSplitView)
that overrides dividerColor (or drawDivider(in:)) to return/paint clear, then
replace only the specific NavigationSplitView's split view instance with this
subclass (or ensure the NavigationSplitView uses it) rather than recursing from
window.contentView; update hideDividers to target the navigation split view or
its container and remove the setValue(_:forKey:) KVC call and broad traversal.
- Around line 3487-3506: The code sets
window.styleMask.insert(.fullSizeContentView) unconditionally, which contradicts
the macOS 26+ branch that expects the system titlebar to handle dragging without
fullSizeContentView; either make the insertion conditional (only insert
.fullSizeContentView in the else branch for pre-macOS 26) or update the comment
above the macOS 26+ branch to explicitly explain why fullSizeContentView is
still required there; locate the availability checks and the unconditional call
to window.styleMask.insert(.fullSizeContentView) and change it to conditional
insertion tied to the macOS 26 availability or revise the comment near the macOS
26 block to describe the actual layout/drag implications when
.fullSizeContentView is present.

In `@Sources/WindowToolbarController.swift`:
- Around line 193-222: Replace the hard-coded UI strings in the toolbar item
construction with localized strings using String(localized:defaultValue:), e.g.
for items identified by sidebarToggleIdentifier, notificationsIdentifier, and
newTabIdentifier update item.label, item.toolTip, and NSImage(...
accessibilityDescription:) to use String(localized: "TOOLBAR_SIDEBAR_LABEL",
defaultValue: "Sidebar") (and similar keys for tooltip and accessibility text
for each item); ensure the code changes occur in the toolbar item creation block
(the method that builds NSToolbarItem instances and sets item.target/action like
toggleSidebarAction, toggleNotificationsAction, newTabAction) and add the
corresponding keys and translations into Resources/Localizable.xcstrings for
English and Japanese.

In `@Sources/Workspace.swift`:
- Around line 6781-6789: The code unconditionally sets tabBarHeight to 0 on
macOS 26 which hides tabs for all panes; change the helper so tabBarHeight is
computed per-pane (or accept a parameter like tabCount or isSingleTab) and only
return 0 when the pane has a single tab on macOS 26. Update the
BonsplitConfiguration.Appearance creation logic (the call site that currently
uses Self.currentSplitButtonTooltips()) to pass the pane-specific flag or count,
and alter the helper that currently sets hideTabBar to instead check both OS
version and whether the pane is single-tab before returning tabBarHeight 0; keep
the default 33 for multi-tab panes.

---

Nitpick comments:
In `@Sources/TerminalWindowPortal.swift`:
- Around line 1504-1516: The corner radius and maskedCorners updates on
hostedView.layer (the block checking hasSidebarToLeft and setting
hostedView.layer?.cornerRadius / hostedView.layer?.maskedCorners) must be
performed inside a CATransaction with actions disabled (or moved into the
existing CATransaction that updates frame/bounds) to prevent implicit animations
during geometry sync; wrap the radius/maskedCorners assignment in a
CATransaction.begin()/setDisableActions(true)/commit() pair (or relocate those
lines into the existing disabled-action transaction) so changes to cornerRadius
and maskedCorners aren’t implicitly animated.

In `@Sources/WindowToolbarController.swift`:
- Around line 230-236: The discard assignments are unnecessary: remove the `_ =`
when calling AppDelegate.shared?.sidebarState?.toggle() inside
toggleSidebarAction() and when calling
AppDelegate.shared?.toggleNotificationsPopover(animated: true) inside
toggleNotificationsAction() so each method is invoked directly (i.e., call
sidebarState?.toggle() and toggleNotificationsPopover(animated: true) without
assigning their Void results).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b13684ed-2261-47a7-9ab5-3bfe6eab8317

📥 Commits

Reviewing files that changed from the base of the PR and between 179b16c and 10f2d7a.

📒 Files selected for processing (8)
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TerminalWindowPortal.swift
  • Sources/WindowToolbarController.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • vendor/bonsplit

Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
Comment on lines +2901 to +2922
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
Button {
_ = AppDelegate.shared?.toggleNotificationsPopover(animated: true)
} label: {
Image(systemName: "bell")
}
.buttonStyle(.accessoryBarAction)
.accessibilityIdentifier("toolbar.notifications")

Button {
if let appDelegate = AppDelegate.shared {
if appDelegate.addWorkspaceInPreferredMainWindow(debugSource: "toolbar.newTab") == nil {
appDelegate.openNewMainWindow(nil)
}
}
} label: {
Image(systemName: "plus")
}
.buttonStyle(.accessoryBarAction)
.accessibilityIdentifier("toolbar.newTab")
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 6, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Give the new toolbar icons localized action names.

These are image-only buttons, so VoiceOver/help text falls back to the SF Symbol name (or no useful action name). Please switch them to localized Labels and keep the icon-only appearance with .labelStyle(.iconOnly).

Possible fix
                 .toolbar {
                     ToolbarItemGroup(placement: .primaryAction) {
                         Button {
                             _ = AppDelegate.shared?.toggleNotificationsPopover(animated: true)
                         } label: {
-                            Image(systemName: "bell")
+                            Label(
+                                String(localized: "toolbar.notifications.button", defaultValue: "Notifications"),
+                                systemImage: "bell"
+                            )
                         }
+                        .labelStyle(.iconOnly)
+                        .help(String(localized: "toolbar.notifications.button", defaultValue: "Notifications"))
                         .buttonStyle(.accessoryBarAction)
                         .accessibilityIdentifier("toolbar.notifications")
 
                         Button {
                             if let appDelegate = AppDelegate.shared {
                                 if appDelegate.addWorkspaceInPreferredMainWindow(debugSource: "toolbar.newTab") == nil {
                                     appDelegate.openNewMainWindow(nil)
                                 }
                             }
                         } label: {
-                            Image(systemName: "plus")
+                            Label(
+                                String(localized: "toolbar.newWorkspace.button", defaultValue: "New Workspace"),
+                                systemImage: "plus"
+                            )
                         }
+                        .labelStyle(.iconOnly)
+                        .help(String(localized: "toolbar.newWorkspace.button", defaultValue: "New Workspace"))
                         .buttonStyle(.accessoryBarAction)
                         .accessibilityIdentifier("toolbar.newTab")
                     }
                 }

As per coding guidelines All user-facing strings must be localized using String(localized: "key.name", defaultValue: "English text") for every string shown in the UI. Keys must go in Resources/Localizable.xcstrings with translations for all supported languages (English and Japanese).

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

In `@Sources/ContentView.swift` around lines 2901 - 2922, The toolbar's image-only
Buttons (the bell and plus in the ToolbarItemGroup) lack localized action names
for VoiceOver; replace each Button's label Image block with a localized Label
using String(localized: "toolbar.notifications", defaultValue: "Notifications")
and String(localized: "toolbar.newTab", defaultValue: "New Tab") (or similar
keys), then apply .labelStyle(.iconOnly) to keep the icon-only appearance;
ensure the same accessibilityIdentifier values ("toolbar.notifications" and
"toolbar.newTab") remain and add corresponding keys to
Resources/Localizable.xcstrings with English and Japanese translations.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The SwiftUI toolbar buttons carry .accessibilityLabel(String(localized:...)) and .accessibilityIdentifier(...); the AppKit NSToolbarItems use String(localized:) for label, toolTip, and accessibilityDescription (WindowToolbarController.swift:196-219). VoiceOver picks up the localized names.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@rodchristiansen Thanks for the clarification — agreed.

I checked the current macOS 26+ SwiftUI toolbar in Sources/ContentView.swift:

  • Notifications button: has .accessibilityIdentifier("toolbar.notifications") and a localized .accessibilityLabel("toolbar.notifications.label", defaultValue: "Notifications").
  • New Tab button: has .accessibilityIdentifier("toolbar.newTab") and a localized .accessibilityLabel("toolbar.newTab.label", defaultValue: "New Tab").
  • The rest of the primary actions also carry localized .accessibilityLabel.
  • The navigation sidebar toggle additionally defines .help, and WindowToolbarController.swift sets localized label/toolTip/accessibilityDescription for its NSToolbarItems.

Given that, VoiceOver already announces proper, localized names. No change is required, and I’m withdrawing the earlier suggestion.

Optional polish (not required): you could add .help to the two SwiftUI buttons for consistent tooltips in the SwiftUI path:

  • Notifications: .help(String(localized: "toolbar.notifications.label", defaultValue: "Notifications"))
  • New Tab: .help(String(localized: "toolbar.newTab.label", defaultValue: "New Tab"))

✏️ Learnings added
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2918-3021
Timestamp: 2026-04-14T20:00:12.468Z
Learning: Repo: manaflow-ai/cmux — Accessibility convention: For toolbar actions in macOS 26+ SwiftUI .toolbar (Sources/ContentView.swift), use localized .accessibilityLabel (and identifiers) for VoiceOver; AppKit NSToolbarItems in Sources/WindowToolbarController.swift set localized label/toolTip/accessibilityDescription. Label(...).labelStyle(.iconOnly) is not required here as VO picks up the localized accessibilityLabel.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to Sources/**/GhosttyTerminalView.swift : Mount `SurfaceSearchOverlay` from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`, to maintain proper layering during split/workspace churn

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn.

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1805
File: Sources/ContentView.swift:662-672
Timestamp: 2026-03-19T10:48:34.765Z
Learning: Repo manaflow-ai/cmux — Sources/ContentView.swift: FileDropOverlayView is anchored to the window’s contentView (theme frame). Within its bounds, the only non-terminal/non-browser area is the Sidebar, so performDragOperation’s folder-drop fallback (calling onDrop when no terminal/web view is under the cursor) is effectively sidebar-only and does not require additional hit-testing for the titlebar or other regions.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T03:20:48.079Z
Learning: Applies to **/*TerminalView.swift : Terminal find layering contract: SurfaceSearchOverlay must be mounted from GhosttySurfaceScrollView in Sources/GhosttyTerminalView.swift (AppKit portal layer), not from SwiftUI panel containers

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : Use the Debug menu (macOS menu bar, DEBUG builds only) for visual iteration. To add a debug toggle or visual option: create an `NSWindowController` subclass with a `shared` singleton, add it to the 'Debug Windows' menu in `Sources/cmuxApp.swift`, and add a SwiftUI view with `AppStorage` bindings for live changes

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T01:01:43.511Z
Learning: Applies to {**/GhosttyTerminalView.swift,**/TerminalPanelView.swift} : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2646
File: Sources/ContentView.swift:0-0
Timestamp: 2026-04-14T19:57:19.057Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — SidebarTabDropDelegate.performDrop(info:) funnels all sidebar drag-and-drop through a section-aware path: (1) if the drop target is inside a section, compute the insertion index from the destination section’s workspaceIds after removing the dragged id, then call TabManager.moveWorkspaceToSection(tabId:sectionId:atIndex:); (2) if the target is ungrouped, first remove the workspace from its source section via TabManager.removeWorkspaceFromSection(tabId:) and then call TabManager.reorderWorkspace(tabId:toIndex:). Same-index same-section drops intentionally no-op. This ensures cross-section moves update membership and same-section downward moves don’t overshoot.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2646
File: Sources/ContentView.swift:9956-9963
Timestamp: 2026-04-14T19:57:01.062Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — TabItemView.index must always reflect the flat order from TabManager.tabs (not the sidebar section/display order). VerticalTabsSidebar builds a tabIndexById from tabs.enumerated() and passes index: tabIndexById[tab.id] to all TabItemView call sites (pinned, ungrouped, and sectioned). Future changes should preserve this contract so shift-selection and moveBy(_:) continue to work correctly.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T00:05:51.436Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/ContentView.swift : In `TabItemView` in `ContentView.swift`: uses `Equatable` conformance + `.equatable()` to skip body re-evaluation during typing. Do not add `EnvironmentObject`, `ObservedObject` (besides `tab`), or `Binding` properties without updating the `==` function. Do not remove `.equatable()` from the ForEach call site. Do not read `tabManager` or `notificationStore` in the body; use precomputed `let` parameters instead.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/*.swift : All user-facing strings must be localized using `String(localized: "key.name", defaultValue: "English text")` for every string shown in the UI (labels, buttons, menus, dialogs, tooltips, error messages). Keys must go in `Resources/Localizable.xcstrings` with translations for all supported languages (English and Japanese). Never use bare string literals in SwiftUI `Text()`, `Button()`, alert titles, etc.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : All user-facing strings must be localized. Use `String(localized: "key.name", defaultValue: "English text")` for every string shown in the UI (labels, buttons, menus, dialogs, tooltips, error messages). Keys go in `Resources/Localizable.xcstrings` with translations for all supported languages (currently English and Japanese)

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/Update/UpdateTitlebarAccessory.swift:924-926
Timestamp: 2026-03-04T14:04:40.577Z
Learning: In manaflow-ai/cmux (Swift/SwiftUI macOS app), the notifications empty-state localization keys are intentionally different across two views:
- `NotificationsPage.swift` uses key `"notifications.empty.description"` with text "Desktop notifications will appear here for quick review." (full-page view).
- `UpdateTitlebarAccessory.swift` uses key `"notifications.empty.subtitle"` with text "Desktop notifications will appear here." (space-constrained titlebar popover).
Both keys have correct Japanese translations in the .xcstrings catalog. The difference in key names and text is by design, not an inconsistency.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : Never use bare string literals in SwiftUI `Text()`, `Button()`, alert titles, or other UI components; use localized strings instead

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T19:00:12.434Z
Learning: Repo: manaflow-ai/cmux — Sources/GroupHeaderView.swift and Sources/ContentView.swift — Palette color entry labels (`Text(entry.name)`) use dynamic palette data loaded at runtime and cannot be statically keyed with `String(localized:)`. This matches the existing workspace color picker pattern in ContentView.swift. Palette name localization would require restructuring the color palette system and is intentionally deferred; do not flag `entry.name` as a missing localization in this codebase.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 318
File: Sources/Panels/BrowserPanel.swift:5511-5521
Timestamp: 2026-03-17T05:34:52.853Z
Learning: In manaflow-ai/cmux, import-flow UI strings in Sources/Panels/BrowserPanel.swift must use String(localized:..., defaultValue:...) with browser.import.* keys, while brand/engine names (e.g., Google, DuckDuckGo) remain unlocalized per project convention.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2575
File: Sources/cmuxApp.swift:0-0
Timestamp: 2026-04-06T09:01:51.979Z
Learning: Repo: manaflow-ai/cmux — File: Sources/cmuxApp.swift — SettingsView now uses SwiftUI `.searchable(text:placement:prompt:)` for the sidebar search, which provides a native (accessible) clear button. Do not flag missing accessibility on a custom clear button in this view going forward.

Learnt from: ariel-wiz
Repo: manaflow-ai/cmux PR: 543
File: CLI/cmux.swift:2006-2007
Timestamp: 2026-03-11T09:27:14.201Z
Learning: In manaflow-ai/cmux (CLI/cmux.swift), all CLI error messages and help text use plain Swift string literals — there is no `String(localized:...)` usage anywhere in the repository, and no `Localizable.xcstrings` file exists. Do not flag CLI error/help strings as missing localization for this project.

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1795
File: Sources/ContentView.swift:1392-1395
Timestamp: 2026-03-19T09:21:24.369Z
Learning: Repo manaflow-ai/cmux — Notifications UI: Production notifications are shown via NSPopover (toggleNotificationsPopover in Sources/Update/UpdateTitlebarAccessory.swift). The SidebarSelectionState.selection == .notifications path in Sources/ContentView.swift is test/scaffolding-only (set in AppDelegate for tests) and isn’t used by user actions; .tabs remains active in normal flows. Therefore, folder-drop handling doesn’t need to flip selection.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/AppDelegate.swift:1955-1970
Timestamp: 2026-03-21T07:13:41.796Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.registerMainWindow ownership pattern: the primary window registers from ContentView.onAppear and passes the SwiftUI-owned FileBrowserDrawerState; secondary windows created via AppDelegate.createMainWindow construct and pass their own FileBrowserDrawerState. This mirrors SidebarState and avoids re-registration mismatches.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-08T03:36:30.160Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.FileBrowserDrawerState threading pattern (PR `#1909`, commit e0e57809): FileBrowserDrawerState must be threaded through AppDelegate.configure() as a weak stored property (matching the sidebarState pattern), passed through both configure() call sites, with registerMainWindow parameter made non-optional. The fallback `?? FileBrowserDrawerState()` must NOT be used as it creates detached instances that are not properly owned by the window context.

Learnt from: lucasward
Repo: manaflow-ai/cmux PR: 1903
File: Sources/ContentView.swift:13069-13113
Timestamp: 2026-03-21T06:23:38.764Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, DraggableFolderNSView.updateIcon() intentionally applies the "sidebarMonochromeIcons" setting only on view creation (no live observer). Do not add defaults observers/AppStorage for live refresh in feature-scoped PRs; a live-refresh can be considered in a separate follow-up.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2884
File: Sources/AppIconDockTilePlugin.swift:16-19
Timestamp: 2026-04-14T08:50:27.729Z
Learning: Repo: manaflow-ai/cmux — Sources/AppIconDockTilePlugin.swift — Two-layer icon persistence contract (PR `#2884`):
- App process (Sources/cmuxApp.swift): AppIconMode.automatic returns nil for imageName to avoid writing custom icons to the bundle from the app side (runtime-only swaps).
- Dock plugin (Sources/AppIconDockTilePlugin.swift): DockTileAppIconMode.automatic intentionally returns a concrete NSImage.Name (AppIconDark or AppIconLight) based on the current effective appearance, so the plugin persists an appearance-matched icon to the bundle. This ensures the Dock shows the correct icon after force-kill/quit instead of falling back to the static bundle asset.
- Do NOT flag DockTileAppIconMode.automatic returning a concrete image name as a bug; it is the intentional Dock-side persistence path. Only when the user explicitly selects light or dark mode is the plugin's behavior identical to cmuxApp.swift. The .automatic branch must NOT return nil from the plugin.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2876
File: Sources/cmuxApp.swift:3812-3821
Timestamp: 2026-04-14T04:50:37.548Z
Learning: Repo: manaflow-ai/cmux — App icon contract: In Sources/cmuxApp.swift, AppIconSettings.automatic uses the bundle .icon only when AppearanceSettings is .system; when the app appearance is forced (.light/.dark), it switches to a runtime light/dark image. cmuxApp.applyAppearance() re-applies AppIconSettings.applyIcon(.automatic) on appearance changes to keep the icon in sync across hosts (Icon Composer supported and fallback).

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 2505
File: Sources/GhosttyTerminalView.swift:1694-1737
Timestamp: 2026-04-06T07:16:45.980Z
Learning: Repo: manaflow-ai/cmux — Background rendering contract
- In Sources/GhosttyTerminalView.swift, GhosttyApp.loadDefaultConfigFilesWithLegacyFallback(_:) sets usesHostLayerBackground = false when a Ghostty background-image is configured (injects "macos-background-from-layer = false"); otherwise it enables the host-layer path.
- GhosttyNSView.applySurfaceBackground() reads GhosttyApp.shared.usesHostLayerBackground and sets GhosttySurfaceScrollView’s background to .clear when false, avoiding a second solid-color fill under Ghostty’s image compositor.
- After config reload, terminal panels reapply the correct host-layer/background mode to stay consistent with the current setting.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: debgotwired
Repo: manaflow-ai/cmux PR: 1149
File: Sources/ContentView.swift:3977-3978
Timestamp: 2026-03-10T09:33:29.814Z
Learning: In manaflow-ai/cmux, for Sources/ContentView.swift, do not modify the sync-seeding behavior of scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:). It is shared infrastructure across all submenus. Treat brief initial flashes as consistent with existing submenus. Any UX improvement (for example making seeding synchronous on a forced corpus refresh) should not be implemented in feature-scoped PRs; instead implement and test such UX changes globally in a dedicated follow-up PR.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Comment thread Sources/ContentView.swift
Comment on lines +3487 to 3506
if #available(macOS 26.0, *) {
window.titlebarAppearsTransparent = false
window.titlebarSeparatorStyle = .none
} else {
window.titlebarAppearsTransparent = true
}
if #available(macOS 26.0, *) {
// On macOS 26 without fullSizeContentView, the system titlebar
// handles drag natively.
window.isMovable = true
window.isMovableByWindowBackground = false
} else {
// Keep window immovable; the sidebar's WindowDragHandleView handles
// drag-to-move via performDrag with temporary movable override.
// isMovableByWindowBackground=true breaks tab reordering, and
// isMovable=true blocks clicks on sidebar buttons in minimal mode.
window.isMovableByWindowBackground = false
window.isMovable = false
}
window.styleMask.insert(.fullSizeContentView)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 6, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

On macOS, what layout effect does NSWindow.StyleMask.fullSizeContentViewhave whentitlebarAppearsTransparent is false, and does it still extend content into the titlebar area?

💡 Result:

NSWindow.StyleMask.fullSizeContentView makes the window’s contentView take the entire window size (including the region normally reserved for the titlebar/toolbar), and you’re expected to use contentLayoutRect / contentLayoutGuide if you want your UI to start below the titlebar/toolbar area. [1]

If titlebarAppearsTransparent is false (the default), the titlebar/toolbar is still drawn on top of that extended content area (typically blurring/obscuring what’s underneath), so your content is still extended into the titlebar region, but it won’t appear “behind” the titlebar as unobscured content. [1][2]


🏁 Script executed:

# Check lines around 2679-2684 to understand the "zero-top-padding macOS 26 path"
sed -n '2670,2700p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 1428


🏁 Script executed:

# Check the full context of the window configuration section (3487-3506 and surrounding)
sed -n '3480,3520p' Sources/ContentView.swift

Repository: manaflow-ai/cmux

Length of output: 2128


🏁 Script executed:

# Search for all fullSizeContentView references to understand how it's used conditionally
rg 'fullSizeContentView' Sources/ContentView.swift -B 2 -A 2

Repository: manaflow-ai/cmux

Length of output: 866


Remove fullSizeContentView from the macOS 26+ path, or update the comment to reflect actual behavior.

The comment at line 3494 says "On macOS 26 without fullSizeContentView, the system titlebar handles drag natively," but line 3506 unconditionally inserts fullSizeContentView for all OS versions. This contradiction is inconsistent with the zero-top-padding design at lines 2679-2684 (which relies on the native glass titlebar to handle safe-area spacing). When fullSizeContentView is active, the contentView extends into the titlebar region, and the layout depends entirely on SwiftUI respecting contentLayoutRect—a fragile dependency that conflicts with the stated intent of letting the macOS 26 native titlebar handle the layout natively.

Either conditionally skip .fullSizeContentView for macOS 26+, or clarify the comment to explain why the native titlebar path still requires extended content bounds.

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

In `@Sources/ContentView.swift` around lines 3487 - 3506, The code sets
window.styleMask.insert(.fullSizeContentView) unconditionally, which contradicts
the macOS 26+ branch that expects the system titlebar to handle dragging without
fullSizeContentView; either make the insertion conditional (only insert
.fullSizeContentView in the else branch for pre-macOS 26) or update the comment
above the macOS 26+ branch to explicitly explain why fullSizeContentView is
still required there; locate the availability checks and the unconditional call
to window.styleMask.insert(.fullSizeContentView) and change it to conditional
insertion tied to the macOS 26 availability or revise the comment near the macOS
26 block to describe the actual layout/drag implications when
.fullSizeContentView is present.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Will tighten the contradictory comment in a follow-up. The code behavior — unconditional .fullSizeContentView — is correct for macOS 26's native titlebar; the comment wording predates the decision.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@rodchristiansen Got it—thanks for clarifying. I’ll skip changes here.

If helpful, here’s a ready-to-paste comment tweak for that block to avoid future confusion:

swift
// macOS 26+: We intentionally keep .fullSizeContentView set.
// The system titlebar draws above the extended content and handles window dragging.
// We keep titlebarAppearsTransparent = false and isMovable = true (with
// isMovableByWindowBackground = false). Geometry/padding is derived from
// contentLayoutRect where needed.

Want me to open a small follow-up issue to track this comment cleanup and link back to this PR and discussion? I can file it and assign it to you.


✏️ Learnings added
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:3586-3604
Timestamp: 2026-04-14T20:00:18.091Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26+, the app intentionally keeps NSWindow.StyleMask.fullSizeContentView enabled even with the native (non-transparent) system titlebar. The titlebar handles dragging natively; isMovable=true and isMovableByWindowBackground=false are used. Any comment suggesting “without fullSizeContentView” is outdated.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1805
File: Sources/ContentView.swift:662-672
Timestamp: 2026-03-19T10:48:34.765Z
Learning: Repo manaflow-ai/cmux — Sources/ContentView.swift: FileDropOverlayView is anchored to the window’s contentView (theme frame). Within its bounds, the only non-terminal/non-browser area is the Sidebar, so performDragOperation’s folder-drop fallback (calling onDrop when no terminal/web view is under the cursor) is effectively sidebar-only and does not require additional hit-testing for the titlebar or other regions.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to Sources/**/GhosttyTerminalView.swift : Mount `SurfaceSearchOverlay` from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`, to maintain proper layering during split/workspace churn

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2124
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-25T08:05:26.034Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — Pattern for “New Window” geometry seed:
In AppDelegate.createMainWindow(initialWorkingDirectory:sessionWindowSnapshot:), compute the existingFrame using preferredMainWindowContextForWorkspaceCreation(debugSource: "createMainWindow.initialGeometry") and then resolvedWindow(for:) rather than relying on NSApp.keyWindow or the first registered mainWindowContext. Rationale: ensures the new window inherits size from the intended main-terminal window even when an auxiliary window is key, and keeps behavior consistent with showOpenFolderPanel().

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/AppDelegate.swift:1955-1970
Timestamp: 2026-03-21T07:13:41.796Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.registerMainWindow ownership pattern: the primary window registers from ContentView.onAppear and passes the SwiftUI-owned FileBrowserDrawerState; secondary windows created via AppDelegate.createMainWindow construct and pass their own FileBrowserDrawerState. This mirrors SidebarState and avoids re-registration mismatches.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : Use the Debug menu (macOS menu bar, DEBUG builds only) for visual iteration. To add a debug toggle or visual option: create an `NSWindowController` subclass with a `shared` singleton, add it to the 'Debug Windows' menu in `Sources/cmuxApp.swift`, and add a SwiftUI view with `AppStorage` bindings for live changes

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2646
File: Sources/ContentView.swift:0-0
Timestamp: 2026-04-14T19:57:19.057Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — SidebarTabDropDelegate.performDrop(info:) funnels all sidebar drag-and-drop through a section-aware path: (1) if the drop target is inside a section, compute the insertion index from the destination section’s workspaceIds after removing the dragged id, then call TabManager.moveWorkspaceToSection(tabId:sectionId:atIndex:); (2) if the target is ungrouped, first remove the workspace from its source section via TabManager.removeWorkspaceFromSection(tabId:) and then call TabManager.reorderWorkspace(tabId:toIndex:). Same-index same-section drops intentionally no-op. This ensures cross-section moves update membership and same-section downward moves don’t overshoot.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T03:20:48.079Z
Learning: Applies to **/*TerminalView.swift : Terminal find layering contract: SurfaceSearchOverlay must be mounted from GhosttySurfaceScrollView in Sources/GhosttyTerminalView.swift (AppKit portal layer), not from SwiftUI panel containers

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 2505
File: Sources/GhosttyTerminalView.swift:1694-1737
Timestamp: 2026-04-06T07:16:45.980Z
Learning: Repo: manaflow-ai/cmux — Background rendering contract
- In Sources/GhosttyTerminalView.swift, GhosttyApp.loadDefaultConfigFilesWithLegacyFallback(_:) sets usesHostLayerBackground = false when a Ghostty background-image is configured (injects "macos-background-from-layer = false"); otherwise it enables the host-layer path.
- GhosttyNSView.applySurfaceBackground() reads GhosttyApp.shared.usesHostLayerBackground and sets GhosttySurfaceScrollView’s background to .clear when false, avoiding a second solid-color fill under Ghostty’s image compositor.
- After config reload, terminal panels reapply the correct host-layer/background mode to stay consistent with the current setting.

Learnt from: lucasward
Repo: manaflow-ai/cmux PR: 1903
File: Sources/ContentView.swift:13069-13113
Timestamp: 2026-03-21T06:23:38.764Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, DraggableFolderNSView.updateIcon() intentionally applies the "sidebarMonochromeIcons" setting only on view creation (no live observer). Do not add defaults observers/AppStorage for live refresh in feature-scoped PRs; a live-refresh can be considered in a separate follow-up.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2475
File: Sources/ContentView.swift:0-0
Timestamp: 2026-04-03T07:19:36.497Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, the command‑palette rename flow uses a single-line SwiftUI TextField via commandPaletteEditorField(style: .singleLine(...)); the multiline NSTextView editor is only used for the workspace description input. Do not flag newline persistence for rename.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2646
File: Sources/CmuxConfigExecutor.swift:107-118
Timestamp: 2026-04-14T19:56:39.598Z
Learning: Repo: manaflow-ai/cmux — In Sources/CmuxConfigExecutor.swift, executeWorkspaceCommand for wsDef.target == .current intentionally uses closePanel(panelId, force: true) before calling applyCustomLayout. This is by design: soft-closing (force: false) leaves detached panes that applyCustomLayout cannot reconcile. If a non-destructive autoApply variant is needed in the future, it should be opted into explicitly via a separate flag rather than changing the default force: true behavior.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/Update/UpdateTitlebarAccessory.swift:924-926
Timestamp: 2026-03-04T14:04:40.577Z
Learning: In manaflow-ai/cmux (Swift/SwiftUI macOS app), the notifications empty-state localization keys are intentionally different across two views:
- `NotificationsPage.swift` uses key `"notifications.empty.description"` with text "Desktop notifications will appear here for quick review." (full-page view).
- `UpdateTitlebarAccessory.swift` uses key `"notifications.empty.subtitle"` with text "Desktop notifications will appear here." (space-constrained titlebar popover).
Both keys have correct Japanese translations in the .xcstrings catalog. The difference in key names and text is by design, not an inconsistency.

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1795
File: Sources/ContentView.swift:1392-1395
Timestamp: 2026-03-19T09:21:24.369Z
Learning: Repo manaflow-ai/cmux — Notifications UI: Production notifications are shown via NSPopover (toggleNotificationsPopover in Sources/Update/UpdateTitlebarAccessory.swift). The SidebarSelectionState.selection == .notifications path in Sources/ContentView.swift is test/scaffolding-only (set in AppDelegate for tests) and isn’t used by user actions; .tabs remains active in normal flows. Therefore, folder-drop handling doesn’t need to flip selection.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-08T03:36:30.160Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.FileBrowserDrawerState threading pattern (PR `#1909`, commit e0e57809): FileBrowserDrawerState must be threaded through AppDelegate.configure() as a weak stored property (matching the sidebarState pattern), passed through both configure() call sites, with registerMainWindow parameter made non-optional. The fallback `?? FileBrowserDrawerState()` must NOT be used as it creates detached instances that are not properly owned by the window context.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2575
File: Sources/cmuxApp.swift:0-0
Timestamp: 2026-04-06T09:01:51.979Z
Learning: Repo: manaflow-ai/cmux — File: Sources/cmuxApp.swift — SettingsView now uses SwiftUI `.searchable(text:placement:prompt:)` for the sidebar search, which provides a native (accessible) clear button. Do not flag missing accessibility on a custom clear button in this view going forward.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2884
File: Sources/AppIconDockTilePlugin.swift:16-19
Timestamp: 2026-04-14T08:50:27.729Z
Learning: Repo: manaflow-ai/cmux — Sources/AppIconDockTilePlugin.swift — Two-layer icon persistence contract (PR `#2884`):
- App process (Sources/cmuxApp.swift): AppIconMode.automatic returns nil for imageName to avoid writing custom icons to the bundle from the app side (runtime-only swaps).
- Dock plugin (Sources/AppIconDockTilePlugin.swift): DockTileAppIconMode.automatic intentionally returns a concrete NSImage.Name (AppIconDark or AppIconLight) based on the current effective appearance, so the plugin persists an appearance-matched icon to the bundle. This ensures the Dock shows the correct icon after force-kill/quit instead of falling back to the static bundle asset.
- Do NOT flag DockTileAppIconMode.automatic returning a concrete image name as a bug; it is the intentional Dock-side persistence path. Only when the user explicitly selects light or dark mode is the plugin's behavior identical to cmuxApp.swift. The .automatic branch must NOT return nil from the plugin.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2876
File: Sources/cmuxApp.swift:3812-3821
Timestamp: 2026-04-14T04:50:37.548Z
Learning: Repo: manaflow-ai/cmux — App icon contract: In Sources/cmuxApp.swift, AppIconSettings.automatic uses the bundle .icon only when AppearanceSettings is .system; when the app appearance is forced (.light/.dark), it switches to a runtime light/dark image. cmuxApp.applyAppearance() re-applies AppIconSettings.applyIcon(.automatic) on appearance changes to keep the icon in sync across hosts (Icon Composer supported and fallback).

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: debgotwired
Repo: manaflow-ai/cmux PR: 1149
File: Sources/ContentView.swift:3977-3978
Timestamp: 2026-03-10T09:33:29.814Z
Learning: In manaflow-ai/cmux, for Sources/ContentView.swift, do not modify the sync-seeding behavior of scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:). It is shared infrastructure across all submenus. Treat brief initial flashes as consistent with existing submenus. Any UX improvement (for example making seeding synchronous on a forced corpus refresh) should not be implemented in feature-scoped PRs; instead implement and test such UX changes globally in a dedicated follow-up PR.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Comment thread Sources/ContentView.swift
Comment thread Sources/WindowToolbarController.swift
Comment thread Sources/Workspace.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
Sources/ContentView.swift (1)

15475-15520: ⚠️ Potential issue | 🟠 Major

Avoid private selector dispatch on NSSplitView.

Line 15511 uses setDividerColor: via runtime selector dispatch. This is not a public API—the macOS SDK documents only a dividerColor getter on NSSplitView and does not expose a public setter. Using undocumented selectors is brittle and can break across SDK/runtime updates.

Instead, keep only public APIs (like dividerStyle). If visual hiding is still needed, mask with a SwiftUI/AppKit overlay as a safer fallback.

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

In `@Sources/ContentView.swift` around lines 15475 - 15520, The code uses a
private ObjC selector NSSelectorFromString("setDividerColor:") inside
patchSplitViews(in:) to call setDividerColor: on NSSplitView; remove that
selector dispatch and keep only the public API change (splitView.dividerStyle =
.thin) in SplitViewDividerHiderView.patchSplitViews(in:). If you still need to
visually hide dividers, implement a public-API fallback: add a safe masking
helper (e.g., addDividerMask(to:) called from patchSplitViews) that inserts an
NSView overlay or adjusts the split view’s layer (wantsLayer = true and layer
properties) to cover the divider area, and remove the
NSSelectorFromString/perform(...) block entirely.
🧹 Nitpick comments (3)
Sources/GhosttyTerminalView.swift (1)

9118-9134: Consider tying the leading inset to the same sidebar-presence condition used for corner rounding.

Sources/TerminalWindowPortal.swift only rounds leading corners when a sidebar is detected; keeping this inset always-on may leave unnecessary gutter when the sidebar is hidden. A shared condition (or propagated flag) would keep geometry behavior aligned.

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

In `@Sources/GhosttyTerminalView.swift` around lines 9118 - 9134, The leading
inset is always applied in GhosttyTerminalView despite corner rounding only
happening when the sidebar is visible; update the inset calculation to apply the
inset only when the same "sidebar visible" condition used in
TerminalWindowPortal.swift is true (propagate or query that flag into
GhosttyTerminalView), e.g. compute inset = (isSidebarVisible ? 6 : 0) before
building scrollFrame, and then call setFrameIfNeeded(scrollView, to:
scrollFrame) as before so geometry and corner rounding remain in sync; refer to
the scrollFrame/inset calculation and the call to setFrameIfNeeded(scrollView,
to:) when locating the change.
Sources/WindowToolbarController.swift (1)

230-232: Minor: Unnecessary discard assignment.

SidebarState.toggle() returns Void, so the _ = prefix is superfluous.

♻️ Suggested simplification
 `@objc` private func toggleSidebarAction() {
-    _ = AppDelegate.shared?.sidebarState?.toggle()
+    AppDelegate.shared?.sidebarState?.toggle()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/WindowToolbarController.swift` around lines 230 - 232, The
toggleSidebarAction method contains an unnecessary discard assignment; remove
the "_ =" so the call simply invokes AppDelegate.shared?.sidebarState?.toggle()
because SidebarState.toggle() returns Void. Update the toggleSidebarAction
function to call SidebarState.toggle() without assigning its result (targeting
the invocation in toggleSidebarAction and the SidebarState.toggle call).
Sources/AppDelegate.swift (1)

2601-2605: Decouple the macOS 26 notifications bridge from legacy accessory startup.

titlebarAccessoryController.start() now runs even though attachUpdateAccessory(to:) never attaches on macOS 26. That still keeps the legacy controller’s observer/startup lifecycle alive after the UI moved to the SwiftUI toolbar/popover path.

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

In `@Sources/AppDelegate.swift` around lines 2601 - 2605, The legacy accessory
controller is being started unconditionally via
titlebarAccessoryController.start() even on macOS 26 where
attachUpdateAccessory(to:) is not used; stop starting the legacy controller in
that case to avoid keeping its observers/lifecycle alive. Update the startup
logic so titlebarAccessoryController.start() is only called when the legacy
accessory path will be attached (e.g., guard the call behind the same OS-version
or feature check used by attachUpdateAccessory(to:) — or add a predicate like
shouldUseLegacyAccessory() — and call start() only when that predicate is true),
leaving the SwiftUI toolbar/popover path on macOS 26 untouched. Ensure
references to titlebarAccessoryController and attachUpdateAccessory(to:) are
adjusted consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 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.

In `@Sources/ContentView.swift`:
- Around line 2967-2969: The global notification handler in ContentView
(.onReceive(NotificationCenter.default.publisher(for:
AppDelegate.toggleNotificationsPopoverNotification))) unconditionally toggles
isNotificationsPopoverPresented causing all windows to change; change the
onReceive closure to first cast notification.object to an NSWindow and only
toggle when that window matches this view’s hosting window (i.e., compare
notification.object as? NSWindow to this ContentView’s window/hosting NSWindow),
and update AppDelegate.toggleNotificationsPopoverNotification posts to include
the NSWindow as the notification object so the subscriber can perform the match.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 9123-9130: The inset calculation in GhosttyTerminalView (inside
the macOS 26.0 availability branch where scrollFrame is computed) can produce
negative width/height when bounds are smaller than the hardcoded inset; fix this
by clamping computed width and height to non-negative values (e.g., use
max(bounds.width - inset, 0) and max(bounds.height - inset, 0)) and adjust the
x/y inset application to not exceed the bounds (e.g., compute effectiveInset =
min(inset, bounds.width) for x and min(inset, bounds.height) for y) before
assigning scrollFrame so the resulting CGRect never has negative dimensions.

---

Duplicate comments:
In `@Sources/ContentView.swift`:
- Around line 15475-15520: The code uses a private ObjC selector
NSSelectorFromString("setDividerColor:") inside patchSplitViews(in:) to call
setDividerColor: on NSSplitView; remove that selector dispatch and keep only the
public API change (splitView.dividerStyle = .thin) in
SplitViewDividerHiderView.patchSplitViews(in:). If you still need to visually
hide dividers, implement a public-API fallback: add a safe masking helper (e.g.,
addDividerMask(to:) called from patchSplitViews) that inserts an NSView overlay
or adjusts the split view’s layer (wantsLayer = true and layer properties) to
cover the divider area, and remove the NSSelectorFromString/perform(...) block
entirely.

---

Nitpick comments:
In `@Sources/AppDelegate.swift`:
- Around line 2601-2605: The legacy accessory controller is being started
unconditionally via titlebarAccessoryController.start() even on macOS 26 where
attachUpdateAccessory(to:) is not used; stop starting the legacy controller in
that case to avoid keeping its observers/lifecycle alive. Update the startup
logic so titlebarAccessoryController.start() is only called when the legacy
accessory path will be attached (e.g., guard the call behind the same OS-version
or feature check used by attachUpdateAccessory(to:) — or add a predicate like
shouldUseLegacyAccessory() — and call start() only when that predicate is true),
leaving the SwiftUI toolbar/popover path on macOS 26 untouched. Ensure
references to titlebarAccessoryController and attachUpdateAccessory(to:) are
adjusted consistently.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 9118-9134: The leading inset is always applied in
GhosttyTerminalView despite corner rounding only happening when the sidebar is
visible; update the inset calculation to apply the inset only when the same
"sidebar visible" condition used in TerminalWindowPortal.swift is true
(propagate or query that flag into GhosttyTerminalView), e.g. compute inset =
(isSidebarVisible ? 6 : 0) before building scrollFrame, and then call
setFrameIfNeeded(scrollView, to: scrollFrame) as before so geometry and corner
rounding remain in sync; refer to the scrollFrame/inset calculation and the call
to setFrameIfNeeded(scrollView, to:) when locating the change.

In `@Sources/WindowToolbarController.swift`:
- Around line 230-232: The toggleSidebarAction method contains an unnecessary
discard assignment; remove the "_ =" so the call simply invokes
AppDelegate.shared?.sidebarState?.toggle() because SidebarState.toggle() returns
Void. Update the toggleSidebarAction function to call SidebarState.toggle()
without assigning its result (targeting the invocation in toggleSidebarAction
and the SidebarState.toggle call).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 04b9fbdf-fc7b-405a-a08d-2f294a14cb52

📥 Commits

Reviewing files that changed from the base of the PR and between 10f2d7a and 65db12c.

📒 Files selected for processing (7)
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TerminalWindowPortal.swift
  • Sources/Update/UpdateTitlebarAccessory.swift
  • Sources/WindowToolbarController.swift
  • Sources/Workspace.swift
✅ Files skipped from review due to trivial changes (1)
  • Sources/Update/UpdateTitlebarAccessory.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/TerminalWindowPortal.swift

Comment thread Sources/AppDelegate.swift
Comment on lines +10168 to 10176
/// 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
}
titlebarAccessoryController.toggleNotificationsPopover(animated: animated, anchorView: anchorView)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 7, 2026

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Partially addressed in afb1af9 — the toggle broadcast now scopes to the owning window via userInfo["windowId"] and ContentView filters on that. The dismiss / isShown bridges still route through the legacy controller on 26, which currently reports 'nothing shown' because the SwiftUI @State owns the popover. Tracking for a follow-up that wires the SwiftUI state back through AppDelegate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@rodchristiansen Thanks — I see the toggle is now correctly window-scoped via userInfo["windowId"] and ContentView filters on it. I can complete the bridge so all AppDelegate entry points use the SwiftUI presenter on macOS 26 and stop reporting “nothing shown”.

Plan:

  • Add show/dismiss notifications + a visibilityDidChange notification with userInfo { windowId, visible }.
  • Track visibility per-window in AppDelegate for isShown()/dismiss() return values.
  • Route showNotificationsPopoverFromMenuBar(), dismissNotificationsPopoverIfShown(), and isNotificationsPopoverShown() through the macOS 26 presenter path; keep legacy accessory for < macOS 26.
  • Have ContentView observe show/dismiss and publish visibilityDidChange whenever its @State changes.

Proposed changes

AppDelegate.swift

  • Notifications and keys, visibility tracking, and unified routing
@@
     /// Notification posted on macOS 26 to toggle the SwiftUI notifications popover.
     static let toggleNotificationsPopoverNotification = Notification.Name("cmux.toggleNotificationsPopover")
+    /// Explicit show on macOS 26 (used by menu bar path).
+    static let showNotificationsPopoverNotification = Notification.Name("cmux.showNotificationsPopover")
+    /// Explicit dismiss on macOS 26 (used by Escape/menu bar and imperative callers).
+    static let dismissNotificationsPopoverNotification = Notification.Name("cmux.dismissNotificationsPopover")
+    /// SwiftUI presenter reports state back to AppDelegate on any present/dismiss.
+    static let notificationsPopoverVisibilityDidChangeNotification = Notification.Name("cmux.notificationsPopoverVisibilityDidChange")
+    /// userInfo keys shared across all of the above.
+    static let notificationsPopoverWindowIdUserInfoKey = "windowId"
+    static let notificationsPopoverVisibleUserInfoKey = "visible"
+
+    // Track popover visibility by window for isShown()/dismiss return value on macOS 26.
+    private var notificationsPopoverVisibleByWindowId: [UUID: Bool] = [:]
+    private var notificationsPopoverVisibilityObserver: NSObjectProtocol?
@@
     func applicationDidFinishLaunching(_ notification: Notification) {
         …
         titlebarAccessoryController.start()
         windowDecorationsController.start()
+        // Track SwiftUI-presented popover visibility on macOS 26.
+        installNotificationsPopoverVisibilityObserver()
         …
     }
@@
+    private func installNotificationsPopoverVisibilityObserver() {
+        guard notificationsPopoverVisibilityObserver == nil else { return }
+        notificationsPopoverVisibilityObserver = NotificationCenter.default.addObserver(
+            forName: Self.notificationsPopoverVisibilityDidChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] note in
+            guard let self else { return }
+            guard let userInfo = note.userInfo,
+                  let windowId = userInfo[Self.notificationsPopoverWindowIdUserInfoKey] as? UUID,
+                  let visible = userInfo[Self.notificationsPopoverVisibleUserInfoKey] as? Bool else {
+                return
+            }
+            self.notificationsPopoverVisibleByWindowId[windowId] = visible
+        }
+    }
@@
-    func toggleNotificationsPopover(animated: Bool = true, anchorView: NSView? = nil) {
-        if `#available`(macOS 26.0, *) {
-            // Scope the broadcast to the owning window so a single bell-button
-            // tap or menu command only toggles the popover in the intended
-            // window — not in every cmux window at once.
-            let targetWindow = anchorView?.window ?? NSApp.keyWindow
-            let targetWindowId = targetWindow.flatMap { mainWindowContexts[ObjectIdentifier($0)]?.windowId }
-            var userInfo: [AnyHashable: Any] = [:]
-            if let targetWindowId {
-                userInfo["windowId"] = targetWindowId
-            }
-            NotificationCenter.default.post(
-                name: Self.toggleNotificationsPopoverNotification,
-                object: targetWindow,
-                userInfo: userInfo.isEmpty ? nil : userInfo
-            )
-            return
-        }
-        titlebarAccessoryController.toggleNotificationsPopover(animated: animated, anchorView: anchorView)
+    func toggleNotificationsPopover(animated: Bool = true, anchorView: NSView? = nil) {
+        if `#available`(macOS 26.0, *) {
+            let targetWindow = anchorView?.window ?? NSApp.keyWindow ?? NSApp.mainWindow
+            guard let window = targetWindow, 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)
     }
@@
-    func showNotificationsPopoverFromMenuBar() {
+    func showNotificationsPopoverFromMenuBar() {
         let context: MainWindowContext? = {
             if let keyWindow = NSApp.keyWindow,
                let keyContext = contextForMainTerminalWindow(keyWindow) {
                 return keyContext
             }
             if let first = mainWindowContexts.values.first {
                 return first
             }
             let windowId = createMainWindow()
             return mainWindowContexts.values.first(where: { $0.windowId == windowId })
         }()
 
-        if let context,
-           let window = context.window ?? windowForMainWindowId(context.windowId) {
-            setActiveMainWindow(window)
-            bringToFront(window)
-        }
-
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
-            self?.titlebarAccessoryController.showNotificationsPopover(animated: false)
-        }
+        if let context, let window = context.window ?? windowForMainWindowId(context.windowId) {
+            setActiveMainWindow(window)
+            bringToFront(window)
+            if `#available`(macOS 26.0, *) {
+                NotificationCenter.default.post(
+                    name: Self.showNotificationsPopoverNotification,
+                    object: window,
+                    userInfo: [Self.notificationsPopoverWindowIdUserInfoKey: context.windowId]
+                )
+                return
+            }
+        }
+        // Legacy path (< macOS 26)
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
+            self?.titlebarAccessoryController.showNotificationsPopover(animated: false)
+        }
     }
@@
-    `@discardableResult`
-    func dismissNotificationsPopoverIfShown() -> Bool {
-        titlebarAccessoryController.dismissNotificationsPopoverIfShown()
-    }
+    `@discardableResult`
+    func dismissNotificationsPopoverIfShown() -> Bool {
+        if `#available`(macOS 26.0, *) {
+            let window = NSApp.keyWindow ?? NSApp.mainWindow
+            guard let w = window, let windowId = mainWindowId(for: w) else { return false }
+            let wasVisible = notificationsPopoverVisibleByWindowId[windowId] == true
+            NotificationCenter.default.post(
+                name: Self.dismissNotificationsPopoverNotification,
+                object: w,
+                userInfo: [Self.notificationsPopoverWindowIdUserInfoKey: windowId]
+            )
+            // Optimistically flip to false; ContentView will confirm via visibilityDidChange.
+            notificationsPopoverVisibleByWindowId[windowId] = false
+            return wasVisible
+        }
+        return titlebarAccessoryController.dismissNotificationsPopoverIfShown()
+    }
@@
-    func isNotificationsPopoverShown() -> Bool {
-        titlebarAccessoryController.isNotificationsPopoverShown()
-    }
+    func isNotificationsPopoverShown() -> Bool {
+        if `#available`(macOS 26.0, *) {
+            let window = NSApp.keyWindow ?? NSApp.mainWindow
+            guard let w = window, let windowId = mainWindowId(for: w) else { return false }
+            return notificationsPopoverVisibleByWindowId[windowId] == true
+        }
+        return titlebarAccessoryController.isNotificationsPopoverShown()
+    }
@@
     private func unregisterMainWindow(_ window: NSWindow) {
         …
-        commandPaletteSnapshotByWindowId.removeValue(forKey: removed.windowId)
+        commandPaletteSnapshotByWindowId.removeValue(forKey: removed.windowId)
+        notificationsPopoverVisibleByWindowId.removeValue(forKey: removed.windowId)
         …
     }

ContentView.swift (SwiftUI presenter)

  • Observe the new show/dismiss notifications and publish visibility changes back to AppDelegate. Assuming ContentView already has windowId: UUID and @State var isNotificationsPopoverPresented.
// In onAppear/onReceive setup for notifications presenter on macOS 26:
if `#available`(macOS 26.0, *) {
    let center = NotificationCenter.default
    notificationObservers.append(
        center.addObserver(forName: AppDelegate.toggleNotificationsPopoverNotification, object: nil, queue: .main) { note in
            guard let targetId = (note.userInfo?[AppDelegate.notificationsPopoverWindowIdUserInfoKey] as? UUID),
                  targetId == self.windowId else { return }
            isNotificationsPopoverPresented.toggle()
        }
    )
    notificationObservers.append(
        center.addObserver(forName: AppDelegate.showNotificationsPopoverNotification, object: nil, queue: .main) { note in
            guard let targetId = (note.userInfo?[AppDelegate.notificationsPopoverWindowIdUserInfoKey] as? UUID),
                  targetId == self.windowId else { return }
            isNotificationsPopoverPresented = true
        }
    )
    notificationObservers.append(
        center.addObserver(forName: AppDelegate.dismissNotificationsPopoverNotification, object: nil, queue: .main) { note in
            guard let targetId = (note.userInfo?[AppDelegate.notificationsPopoverWindowIdUserInfoKey] as? UUID),
                  targetId == self.windowId else { return }
            isNotificationsPopoverPresented = false
        }
    )
}

// Whenever `@State` changes (e.g., .popover presenter or onChange):
.onChange(of: isNotificationsPopoverPresented) { newValue in
    if `#available`(macOS 26.0, *) {
        NotificationCenter.default.post(
            name: AppDelegate.notificationsPopoverVisibilityDidChangeNotification,
            object: nil,
            userInfo: [
                AppDelegate.notificationsPopoverWindowIdUserInfoKey: windowId,
                AppDelegate.notificationsPopoverVisibleUserInfoKey: newValue
            ]
        )
    }
}

Result

  • All four entry points (toggle, show-from-menubar, dismiss, isShown) consistently target the active window’s SwiftUI-presented popover on macOS 26; the legacy titlebar accessory remains in use only for < macOS 26.
  • AppDelegate can now reliably dismiss and report visibility without depending on the accessory controller.

Would you like me to push a follow-up commit on this PR with the above changes, or open a tracking issue and land it separately?


✏️ Learnings added
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/AppDelegate.swift:10168-10189
Timestamp: 2026-04-14T20:02:38.217Z
Learning: Repo manaflow-ai/cmux — On macOS 26, the notifications popover is owned by ContentView (State isNotificationsPopoverPresented). AppDelegate APIs (toggle/show/dismiss/isShown) must route via NotificationCenter with a window-scoped userInfo["windowId"] and track visibility per-window, not call UpdateTitlebarAccessoryController. ContentView should observe show/dismiss/toggle and post a visibilityDidChange notification {windowId, visible}.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1795
File: Sources/ContentView.swift:1392-1395
Timestamp: 2026-03-19T09:21:24.369Z
Learning: Repo manaflow-ai/cmux — Notifications UI: Production notifications are shown via NSPopover (toggleNotificationsPopover in Sources/Update/UpdateTitlebarAccessory.swift). The SidebarSelectionState.selection == .notifications path in Sources/ContentView.swift is test/scaffolding-only (set in AppDelegate for tests) and isn’t used by user actions; .tabs remains active in normal flows. Therefore, folder-drop handling doesn’t need to flip selection.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2735
File: Sources/AppDelegate.swift:476-483
Timestamp: 2026-04-09T00:29:20.810Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — Deprecated API handling pattern: wrap NSWorkspace.shared.fullPath(forApplication:) in a private helper (_legacyFullPath(forApplication:)) to centralize usage and intentionally accept the single deprecation warning at that call site, since there is no non-deprecated name-based replacement. Avoid marking the wrapper itself as deprecated to prevent propagating warnings to every wrapper use.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2735
File: Sources/AppDelegate.swift:476-483
Timestamp: 2026-04-09T00:29:06.142Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — The helper `_legacyFullPath(forApplication:)` intentionally remains annotated `available(macOS, deprecated: 11.0)` and calls `NSWorkspace.shared.fullPath(forApplication:)`. Team preference: keep a single, localized deprecation warning at its (single) call site since no non‑deprecated name-based replacement exists. Do not “fix” this by removing the deprecation or silencing the warning in future PRs.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:3586-3604
Timestamp: 2026-04-14T20:00:18.091Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26+, the app intentionally keeps NSWindow.StyleMask.fullSizeContentView enabled even with the native (non-transparent) system titlebar. The titlebar handles dragging natively; isMovable=true and isMovableByWindowBackground=false are used. Any comment suggesting “without fullSizeContentView” is outdated.

Learnt from: gaelic-ghost
Repo: manaflow-ai/cmux PR: 1771
File: Sources/AppDelegate.swift:9155-9171
Timestamp: 2026-03-23T03:31:05.252Z
Learning: Repo: manaflow-ai/cmux — Sources/AppDelegate.swift — In AppDelegate.handleCustomShortcut(event:), if hasEventWindowContext is true and synchronizeShortcutRoutingContext(event:) fails, only allow auxiliary windows to bypass routing for close-window shortcuts. Use shouldAllowAuxiliaryWindowShortcutWithoutMainRoute(event:eventTargetWindow:) (which calls isAuxiliaryWindowCloseShortcut) to permit Cmd+W/close-window only; for all other shortcuts, return false to avoid mutating the wrong main-window tabManager.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : Use the Debug menu (macOS menu bar, DEBUG builds only) for visual iteration. To add a debug toggle or visual option: create an `NSWindowController` subclass with a `shared` singleton, add it to the 'Debug Windows' menu in `Sources/cmuxApp.swift`, and add a SwiftUI view with `AppStorage` bindings for live changes

Learnt from: Horacehxw
Repo: manaflow-ai/cmux PR: 1980
File: Sources/AppDelegate.swift:2454-2458
Timestamp: 2026-04-06T09:33:40.789Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — In activateUITestAppIfNeeded() for macOS 14+, prefer NSRunningApplication.current.activate(options: [.activateAllWindows]) without .activateIgnoringOtherApps; the deprecated option is only used on older macOS. A future follow-up may migrate to the modern activation API.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/AppDelegate.swift:1955-1970
Timestamp: 2026-03-21T07:13:41.796Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.registerMainWindow ownership pattern: the primary window registers from ContentView.onAppear and passes the SwiftUI-owned FileBrowserDrawerState; secondary windows created via AppDelegate.createMainWindow construct and pass their own FileBrowserDrawerState. This mirrors SidebarState and avoids re-registration mismatches.

Learnt from: Horacehxw
Repo: manaflow-ai/cmux PR: 2043
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-04-06T09:32:26.967Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — UI test fallback window pattern: during XCTest launch stabilization, the app may force-create a fallback main window when NSApp.windows is empty. The window is tracked via `private weak var fallbackUITestWindow: NSWindow?` and, upon the first real WindowGroup registration in `registerMainWindow(...)`, if a second main window exists and the registering window differs, the fallback is closed and the reference cleared to avoid two main windows during tests.

Learnt from: EtanHey
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-17T15:14:58.452Z
Learning: Repo: manaflow-ai/cmux — Sources/TerminalNotificationStore.swift — The `asyncAfter` protection for `Published` `authorizationState` mutations is only required for init-time and passive query callbacks (e.g., `getNotificationSettings` callback in `refreshAuthorizationStatus`) that can fire during the window setup constraint pass and trigger an NSGenericException. User-action-triggered mutation sites (`ensureAuthorization` ~line 1084 and `requestAuthorization` ~line 1140) are safe to use plain `DispatchQueue.main.async` because AppKit window constraints are fully converged by the time any user interaction begins.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/AppDelegate.swift:2196-2200
Timestamp: 2026-04-03T03:36:45.112Z
Learning: Repo: manaflow-ai/cmux — In Sources/AppDelegate.swift, when KeyboardShortcutSettings.didChangeNotification fires, AppDelegate must clear configured-chord caches (pendingConfiguredShortcutChord and activeConfiguredShortcutChordPrefixForCurrentEvent) via clearConfiguredShortcutChordState() before refreshing tooltips/UI. Also clear chord state on applicationWillResignActive to avoid cross-activity leakage. Verified by cmuxTests/AppDelegateShortcutRoutingTests.swift::testShortcutChangeClearsPendingConfiguredChord.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/Update/UpdateTitlebarAccessory.swift:924-926
Timestamp: 2026-03-04T14:04:40.577Z
Learning: In manaflow-ai/cmux (Swift/SwiftUI macOS app), the notifications empty-state localization keys are intentionally different across two views:
- `NotificationsPage.swift` uses key `"notifications.empty.description"` with text "Desktop notifications will appear here for quick review." (full-page view).
- `UpdateTitlebarAccessory.swift` uses key `"notifications.empty.subtitle"` with text "Desktop notifications will appear here." (space-constrained titlebar popover).
Both keys have correct Japanese translations in the .xcstrings catalog. The difference in key names and text is by design, not an inconsistency.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-08T03:36:30.160Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.FileBrowserDrawerState threading pattern (PR `#1909`, commit e0e57809): FileBrowserDrawerState must be threaded through AppDelegate.configure() as a weak stored property (matching the sidebarState pattern), passed through both configure() call sites, with registerMainWindow parameter made non-optional. The fallback `?? FileBrowserDrawerState()` must NOT be used as it creates detached instances that are not properly owned by the window context.

Learnt from: SuperManfred
Repo: manaflow-ai/cmux PR: 1150
File: Sources/AppDelegate.swift:7394-7401
Timestamp: 2026-03-10T10:24:14.017Z
Learning: Repo: manaflow-ai/cmux — For Cmd+W behavior with browser popups: BrowserPopupPanel.performKeyEquivalent is the primary handler to close popups. AppDelegate.handleCustomShortcut(_:), in its Cmd+W fallback, must check both NSApp.keyWindow and event.window for identifier "cmux.browser-popup" and close it if found, before routing to workspace/settings close logic.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2889-2911
Timestamp: 2026-04-14T19:59:52.026Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26, the NavigationSplitView sidebar width is synchronized back to model state via a GeometryReader that updates sidebarWidth and SidebarState.persistedWidth on width changes. Do not flag “write-only sidebar width” drift for this path going forward.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 2505
File: Sources/AppDelegate.swift:5636-5642
Timestamp: 2026-04-06T07:18:38.685Z
Learning: In the AppDelegate.swift keyDown focus-repair path (and related surface keyboard-focus matching like GhosttySurfaceScrollView.swift), do not dereference NSTextView.delegate when it is unsafe-unretained. Instead, resolve the field-editor ownership via cmuxFieldEditorOwnerView(_). For keyboard-focus matching, prefer superview/nextResponder traversal or hostedView.responderMatchesPreferredKeyboardFocus(...), and ensure any matches are performed using the owned responder chain rather than reading an unsafe delegate pointer.

Learnt from: johnhanks1
Repo: manaflow-ai/cmux PR: 1556
File: Sources/AppDelegate.swift:7878-7888
Timestamp: 2026-03-17T01:11:15.590Z
Learning: Repo: manaflow-ai/cmux — Sources/AppDelegate.swift — In AppDelegate.handleCustomShortcut(event:), when detecting an external keyboard input-source change (via KeyboardLayout.id), the skip branch must not update lastKeyboardInputSourceId; it should only log and return false. Update lastKeyboardInputSourceId only after passing the guard into normal shortcut handling. Rationale: prevents same-event re-entry (e.g., via handleBrowserSurfaceKeyEquivalent from NSWindow.performKeyEquivalent) from bypassing the skip and inadvertently processing app shortcuts.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2124
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-25T08:05:26.034Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — Pattern for “New Window” geometry seed:
In AppDelegate.createMainWindow(initialWorkingDirectory:sessionWindowSnapshot:), compute the existingFrame using preferredMainWindowContextForWorkspaceCreation(debugSource: "createMainWindow.initialGeometry") and then resolvedWindow(for:) rather than relying on NSApp.keyWindow or the first registered mainWindowContext. Rationale: ensures the new window inherits size from the intended main-terminal window even when an auxiliary window is key, and keeps behavior consistent with showOpenFolderPanel().

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2884
File: Sources/AppIconDockTilePlugin.swift:16-19
Timestamp: 2026-04-14T08:50:27.729Z
Learning: Repo: manaflow-ai/cmux — Sources/AppIconDockTilePlugin.swift — Two-layer icon persistence contract (PR `#2884`):
- App process (Sources/cmuxApp.swift): AppIconMode.automatic returns nil for imageName to avoid writing custom icons to the bundle from the app side (runtime-only swaps).
- Dock plugin (Sources/AppIconDockTilePlugin.swift): DockTileAppIconMode.automatic intentionally returns a concrete NSImage.Name (AppIconDark or AppIconLight) based on the current effective appearance, so the plugin persists an appearance-matched icon to the bundle. This ensures the Dock shows the correct icon after force-kill/quit instead of falling back to the static bundle asset.
- Do NOT flag DockTileAppIconMode.automatic returning a concrete image name as a bug; it is the intentional Dock-side persistence path. Only when the user explicitly selects light or dark mode is the plugin's behavior identical to cmuxApp.swift. The .automatic branch must NOT return nil from the plugin.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2918-3021
Timestamp: 2026-04-14T20:00:12.468Z
Learning: Repo: manaflow-ai/cmux — Accessibility convention: For toolbar actions in macOS 26+ SwiftUI .toolbar (Sources/ContentView.swift), use localized .accessibilityLabel (and identifiers) for VoiceOver; AppKit NSToolbarItems in Sources/WindowToolbarController.swift set localized label/toolTip/accessibilityDescription. Label(...).labelStyle(.iconOnly) is not required here as VO picks up the localized accessibilityLabel.

Learnt from: lucasward
Repo: manaflow-ai/cmux PR: 1903
File: Sources/ContentView.swift:13069-13113
Timestamp: 2026-03-21T06:23:38.764Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, DraggableFolderNSView.updateIcon() intentionally applies the "sidebarMonochromeIcons" setting only on view creation (no live observer). Do not add defaults observers/AppStorage for live refresh in feature-scoped PRs; a live-refresh can be considered in a separate follow-up.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2575
File: Sources/cmuxApp.swift:0-0
Timestamp: 2026-04-06T09:01:51.979Z
Learning: Repo: manaflow-ai/cmux — File: Sources/cmuxApp.swift — SettingsView now uses SwiftUI `.searchable(text:placement:prompt:)` for the sidebar search, which provides a native (accessible) clear button. Do not flag missing accessibility on a custom clear button in this view going forward.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:3491-3493
Timestamp: 2026-03-04T14:06:12.296Z
Learning: In PRs affecting this repository, limit the scope of localization (i18n) changes to Japanese translations for the file Sources/AppDelegate.swift. Do not include UX enhancements (e.g., preferring workspace.customTitle in workspaceDisplayName() or altering move-target labels) in this PR. Open a separate follow-up issue to address any UX-related changes to avoid scope creep and keep localization review focused.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: jt-hsiao
Repo: manaflow-ai/cmux PR: 1423
File: Sources/AppDelegate.swift:11220-11226
Timestamp: 2026-03-14T07:05:52.379Z
Learning: In Sources/AppDelegate.swift (within the manaflow-ai/cmux repo), ensure that NSWindow.cmux_performKeyEquivalent forwards to firstResponderWebView.performKeyEquivalent and returns its Bool unconditionally. This prevents re-entry into SwiftUI’s performKeyEquivalent path that can swallow keys when WKWebView has focus, and aligns with the Command-key routing chain implemented in Sources/Panels/CmuxWebView.swift: (1) route to NSApp.mainMenu.performKeyEquivalent when allowed, (2) fall back to AppDelegate.shared?.handleBrowserSurfaceKeyEquivalent(event) for non-menu shortcuts, (3) fall back to super.performKeyEquivalent. Non-Command keys should still call super directly.

Learnt from: jt-hsiao
Repo: manaflow-ai/cmux PR: 1423
File: Sources/AppDelegate.swift:11220-11226
Timestamp: 2026-03-14T07:05:52.379Z
Learning: In Sources/AppDelegate.swift for the manaflow-ai/cmux repository, Cmd+` (command-backtick) should not be routed directly to the main menu. Do not implement a window/main-menu bypass for this key. Tests will assert this behavior, so ensure routing logic respects this exclusion and that there is no path that bypasses the main menu for Cmd+`. If similar key-command bypass considerations exist in other files, apply the same explicit exclusion only where applicable.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread Sources/AppDelegate.swift
Comment thread Sources/WindowToolbarController.swift
Comment thread Sources/AppDelegate.swift
Comment thread Sources/GhosttyTerminalView.swift
Comment thread Sources/Workspace.swift
Comment thread Sources/cmuxApp.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
Sources/ContentView.swift (1)

2991-2999: ⚠️ Potential issue | 🟠 Major

Scope popover toggle to the owning window.

The notifications popover broadcast is received by all ContentView instances without window filtering. In multi-window sessions, toggling the popover in one window will unintentionally open/close it in unrelated windows. Pass the window to the notification (similar to commandPaletteRequested) and filter subscriptions by comparing against windowIdentifier.

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

In `@Sources/ContentView.swift` around lines 2991 - 2999, The notifications
popover toggle is being broadcast to all ContentView instances; modify the
publisher subscription in ContentView so it only reacts for its owning window:
change the notification payload
(AppDelegate.toggleNotificationsPopoverNotification) to include the source
window identifier (similar to commandPaletteRequested), and in ContentView's
.onReceive for NotificationCenter.default.publisher(for:
AppDelegate.toggleNotificationsPopoverNotification) unpack the notification,
compare the payload's windowIdentifier to this view's windowIdentifier, and only
then toggle isNotificationsPopoverPresented (used with NotificationsPopoverView
and its onDismiss). Ensure the sender sets the windowIdentifier when posting the
notification and keep the filtering logic in ContentView so other windows ignore
it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Around line 2918-2932: The custom toolbar toggle is only created when
sidebarState.isVisible is false while SystemSidebarToggleStripper removes the
system toggle unconditionally, leaving no collapse affordance; update the
toolbar code so the Button in the ToolbarItem (placement: .navigation) that
calls sidebarState.toggle() is rendered regardless of sidebarState.isVisible
(remove the if !sidebarState.isVisible guard) and/or change
SystemSidebarToggleStripper to only strip the system-injected toggle when a
custom toggle exists; keep the same Button action (withAnimation and
sidebarState.toggle()), image ("sidebar.left"), accessibilityIdentifier
("toolbar.toggleSidebar") and accessibility/help strings so the toggle remains
available to collapse the open sidebar.
- Around line 15598-15616: The current patchSplitViews(in:) walks every
NSSplitView in the window and clears dividers globally; instead limit the change
to the outer NavigationSplitView container: update hideDividers() to locate only
the top-level split view that represents the navigation container (search
window.contentView?.subviews for the NSSplitView that is the NavigationSplitView
host, e.g. the direct child corresponding to the app's sidebar split) and call a
new patch function only on that single split view; keep patchSplitViews(in:) but
rename/use it to operate on a single NSSplitView (or add a
patchNavigationSplitView(in:)) so nested split-based content is not modified.
Ensure you reference hideDividers(), patchSplitViews(in:), NavigationSplitView
and window.contentView when making the change.
- Around line 2911-2914: The outer .padding(8) applied to the detail container
creates a dead file-drop margin on macOS 26 because FileDropOverlayView only
forwards drops when the cursor is over a GhosttyNSView or WKWebView; remove the
.padding(8) from the detail: closure and instead apply equivalent spacing inside
terminalContentWithSidebarDropOverlay (i.e., inside the terminal/browser
content) so the visual inset is part of the terminal/browser view rather than
the detail container and drops still hit GhosttyNSView/WKWebView.

---

Duplicate comments:
In `@Sources/ContentView.swift`:
- Around line 2991-2999: The notifications popover toggle is being broadcast to
all ContentView instances; modify the publisher subscription in ContentView so
it only reacts for its owning window: change the notification payload
(AppDelegate.toggleNotificationsPopoverNotification) to include the source
window identifier (similar to commandPaletteRequested), and in ContentView's
.onReceive for NotificationCenter.default.publisher(for:
AppDelegate.toggleNotificationsPopoverNotification) unpack the notification,
compare the payload's windowIdentifier to this view's windowIdentifier, and only
then toggle isNotificationsPopoverPresented (used with NotificationsPopoverView
and its onDismiss). Ensure the sender sets the windowIdentifier when posting the
notification and keep the filtering logic in ContentView so other windows ignore
it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a317528-e622-4d94-b23e-17eb20516479

📥 Commits

Reviewing files that changed from the base of the PR and between e0677c0 and a8709b9.

📒 Files selected for processing (2)
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift

Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift
Comment on lines +2918 to +2932
.toolbar {
if !sidebarState.isVisible {
ToolbarItem(placement: .navigation) {
Button {
withAnimation(.easeInOut(duration: 0.2)) {
_ = sidebarState.toggle()
}
} label: {
Image(systemName: "sidebar.left")
}
.accessibilityIdentifier("toolbar.toggleSidebar")
.accessibilityLabel(String(localized: "toolbar.sidebar.accessibilityLabel", defaultValue: "Toggle Sidebar"))
.help(String(localized: "toolbar.sidebar.tooltip", defaultValue: "Toggle Sidebar"))
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep a toolbar toggle visible while the sidebar is open.

The custom button only renders when the sidebar is already hidden, and SystemSidebarToggleStripper removes the system-injected toggle unconditionally. That leaves no clickable toolbar affordance to collapse an open sidebar.

♻️ Suggested change
-                    if !sidebarState.isVisible {
-                        ToolbarItem(placement: .navigation) {
-                            Button {
-                                withAnimation(.easeInOut(duration: 0.2)) {
-                                    _ = sidebarState.toggle()
-                                }
-                            } label: {
-                                Image(systemName: "sidebar.left")
-                            }
-                            .accessibilityIdentifier("toolbar.toggleSidebar")
-                            .accessibilityLabel(String(localized: "toolbar.sidebar.accessibilityLabel", defaultValue: "Toggle Sidebar"))
-                            .help(String(localized: "toolbar.sidebar.tooltip", defaultValue: "Toggle Sidebar"))
-                        }
-                    }
+                    ToolbarItem(placement: .navigation) {
+                        Button {
+                            withAnimation(.easeInOut(duration: 0.2)) {
+                                sidebarState.toggle()
+                            }
+                        } label: {
+                            Image(systemName: "sidebar.left")
+                        }
+                        .accessibilityIdentifier("toolbar.toggleSidebar")
+                        .accessibilityLabel(String(localized: "toolbar.sidebar.accessibilityLabel", defaultValue: "Toggle Sidebar"))
+                        .help(String(localized: "toolbar.sidebar.tooltip", defaultValue: "Toggle Sidebar"))
+                    }

Also applies to: 15535-15552

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

In `@Sources/ContentView.swift` around lines 2918 - 2932, The custom toolbar
toggle is only created when sidebarState.isVisible is false while
SystemSidebarToggleStripper removes the system toggle unconditionally, leaving
no collapse affordance; update the toolbar code so the Button in the ToolbarItem
(placement: .navigation) that calls sidebarState.toggle() is rendered regardless
of sidebarState.isVisible (remove the if !sidebarState.isVisible guard) and/or
change SystemSidebarToggleStripper to only strip the system-injected toggle when
a custom toggle exists; keep the same Button action (withAnimation and
sidebarState.toggle()), image ("sidebar.left"), accessibilityIdentifier
("toolbar.toggleSidebar") and accessibility/help strings so the toggle remains
available to collapse the open sidebar.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Intentional — when the sidebar is visible, NavigationSplitView owns the native collapse chevron in the column header. The custom button is only needed to reopen the sidebar once it's been hidden (the chevron goes with the column). Keeping both visible would duplicate the toggle.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@rodchristiansen Got it — thanks for clarifying. Given that on macOS 26 the NavigationSplitView shows its own collapse chevron in the sidebar’s column header when the sidebar is visible, keeping our toolbar toggle only for the “reopen when hidden” case (and stripping the system-injected toolbar toggle) avoids duplicate controls. No change needed here; I’ve captured this as a learning so we don’t re-flag it in future reviews.


✏️ Learnings added
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2918-2932
Timestamp: 2026-04-14T20:05:24.226Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift (PR `#2647`): On macOS 26, NavigationSplitView provides a native collapse chevron in the sidebar column header when the sidebar is visible. The code intentionally: (1) shows a custom toolbar toggle only when the sidebar is hidden to reopen it, and (2) uses SystemSidebarToggleStripper to remove the system-injected toolbar toggle to prevent duplication. Do not flag “no collapse affordance” while the sidebar is open.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2889-2911
Timestamp: 2026-04-14T19:59:52.026Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26, the NavigationSplitView sidebar width is synchronized back to model state via a GeometryReader that updates sidebarWidth and SidebarState.persistedWidth on width changes. Do not flag “write-only sidebar width” drift for this path going forward.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:3586-3604
Timestamp: 2026-04-14T20:00:18.091Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26+, the app intentionally keeps NSWindow.StyleMask.fullSizeContentView enabled even with the native (non-transparent) system titlebar. The titlebar handles dragging natively; isMovable=true and isMovableByWindowBackground=false are used. Any comment suggesting “without fullSizeContentView” is outdated.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to Sources/**/GhosttyTerminalView.swift : Mount `SurfaceSearchOverlay` from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`, to maintain proper layering during split/workspace churn

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1805
File: Sources/ContentView.swift:662-672
Timestamp: 2026-03-19T10:48:34.765Z
Learning: Repo manaflow-ai/cmux — Sources/ContentView.swift: FileDropOverlayView is anchored to the window’s contentView (theme frame). Within its bounds, the only non-terminal/non-browser area is the Sidebar, so performDragOperation’s folder-drop fallback (calling onDrop when no terminal/web view is under the cursor) is effectively sidebar-only and does not require additional hit-testing for the titlebar or other regions.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-04-14T20:01:09.378Z
Learning: Repo: manaflow-ai/cmux — macOS 26 UI: NavigationSplitView with SwiftUI .toolbar owns window chrome; WindowToolbarController is not used on macOS 26 and remains as an AppKit fallback. Notifications popover on macOS 26 is driven via AppDelegate.toggleNotificationsPopover posting cmux.toggleNotificationsPopover, which ContentView observes to present a SwiftUI popover.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T03:20:48.079Z
Learning: Applies to **/*TerminalView.swift : Terminal find layering contract: SurfaceSearchOverlay must be mounted from GhosttySurfaceScrollView in Sources/GhosttyTerminalView.swift (AppKit portal layer), not from SwiftUI panel containers

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : Use the Debug menu (macOS menu bar, DEBUG builds only) for visual iteration. To add a debug toggle or visual option: create an `NSWindowController` subclass with a `shared` singleton, add it to the 'Debug Windows' menu in `Sources/cmuxApp.swift`, and add a SwiftUI view with `AppStorage` bindings for live changes

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T01:01:43.511Z
Learning: Applies to {**/GhosttyTerminalView.swift,**/TerminalPanelView.swift} : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2646
File: Sources/ContentView.swift:9956-9963
Timestamp: 2026-04-14T19:57:01.062Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — TabItemView.index must always reflect the flat order from TabManager.tabs (not the sidebar section/display order). VerticalTabsSidebar builds a tabIndexById from tabs.enumerated() and passes index: tabIndexById[tab.id] to all TabItemView call sites (pinned, ungrouped, and sectioned). Future changes should preserve this contract so shift-selection and moveBy(_:) continue to work correctly.

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T21:24:07.678Z
Learning: Repo: manaflow-ai/cmux — Sources/SidebarSelectionState.swift — In `SidebarInteractionController.registerClick(on:location:...)`, call sites that use `onTapGesture` cannot supply real coordinates (SwiftUI's `onTapGesture` does not expose the tap location), so they pass `location: nil`. The `CGPoint?`-nil-gated distance check in `registerClick` is intentionally designed to skip spatial filtering for these callers. Target identity discrimination (`.groupHeader(groupId:)`, `.workspace(workspaceId:parentGroupId:)`, `.emptyArea`) prevents false double-click triggers across distinct targets. Do not flag missing coordinates from `onTapGesture` callsites as a defect; fixing it would require switching to a different gesture recognizer system.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2575
File: Sources/cmuxApp.swift:0-0
Timestamp: 2026-04-06T09:01:51.979Z
Learning: Repo: manaflow-ai/cmux — File: Sources/cmuxApp.swift — SettingsView now uses SwiftUI `.searchable(text:placement:prompt:)` for the sidebar search, which provides a native (accessible) clear button. Do not flag missing accessibility on a custom clear button in this view going forward.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/ContentView.swift:8917-8921
Timestamp: 2026-04-03T03:35:56.499Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — ShortcutHintModifierPolicy.shouldShowHints(for:) reveals sidebar/titlebar shortcut hints when the current modifier flags exactly equal KeyboardShortcutSettings.shortcut(for: .selectWorkspaceByNumber).modifierFlags, and returns false for chorded-number mappings. This follows the configured workspace-number modifier (not strictly Command-only). TabItemView’s close-button suppression via showsModifierShortcutHints therefore tracks the configured modifier. The debug toggle showHintsOnCommandHold predates customizable shortcuts and conceptually means “show hints on holding the configured workspace-number modifier(s).”

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/AppDelegate.swift:10168-10189
Timestamp: 2026-04-14T20:02:38.217Z
Learning: Repo manaflow-ai/cmux — On macOS 26, the notifications popover is owned by ContentView (State isNotificationsPopoverPresented). AppDelegate APIs (toggle/show/dismiss/isShown) must route via NotificationCenter with a window-scoped userInfo["windowId"] and track visibility per-window, not call UpdateTitlebarAccessoryController. ContentView should observe show/dismiss/toggle and post a visibilityDidChange notification {windowId, visible}.

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1795
File: Sources/ContentView.swift:1392-1395
Timestamp: 2026-03-19T09:21:24.369Z
Learning: Repo manaflow-ai/cmux — Notifications UI: Production notifications are shown via NSPopover (toggleNotificationsPopover in Sources/Update/UpdateTitlebarAccessory.swift). The SidebarSelectionState.selection == .notifications path in Sources/ContentView.swift is test/scaffolding-only (set in AppDelegate for tests) and isn’t used by user actions; .tabs remains active in normal flows. Therefore, folder-drop handling doesn’t need to flip selection.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/AppDelegate.swift:1955-1970
Timestamp: 2026-03-21T07:13:41.796Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.registerMainWindow ownership pattern: the primary window registers from ContentView.onAppear and passes the SwiftUI-owned FileBrowserDrawerState; secondary windows created via AppDelegate.createMainWindow construct and pass their own FileBrowserDrawerState. This mirrors SidebarState and avoids re-registration mismatches.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-08T03:36:30.160Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.FileBrowserDrawerState threading pattern (PR `#1909`, commit e0e57809): FileBrowserDrawerState must be threaded through AppDelegate.configure() as a weak stored property (matching the sidebarState pattern), passed through both configure() call sites, with registerMainWindow parameter made non-optional. The fallback `?? FileBrowserDrawerState()` must NOT be used as it creates detached instances that are not properly owned by the window context.

Learnt from: lucasward
Repo: manaflow-ai/cmux PR: 1903
File: Sources/ContentView.swift:13069-13113
Timestamp: 2026-03-21T06:23:38.764Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, DraggableFolderNSView.updateIcon() intentionally applies the "sidebarMonochromeIcons" setting only on view creation (no live observer). Do not add defaults observers/AppStorage for live refresh in feature-scoped PRs; a live-refresh can be considered in a separate follow-up.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2884
File: Sources/AppIconDockTilePlugin.swift:16-19
Timestamp: 2026-04-14T08:50:27.729Z
Learning: Repo: manaflow-ai/cmux — Sources/AppIconDockTilePlugin.swift — Two-layer icon persistence contract (PR `#2884`):
- App process (Sources/cmuxApp.swift): AppIconMode.automatic returns nil for imageName to avoid writing custom icons to the bundle from the app side (runtime-only swaps).
- Dock plugin (Sources/AppIconDockTilePlugin.swift): DockTileAppIconMode.automatic intentionally returns a concrete NSImage.Name (AppIconDark or AppIconLight) based on the current effective appearance, so the plugin persists an appearance-matched icon to the bundle. This ensures the Dock shows the correct icon after force-kill/quit instead of falling back to the static bundle asset.
- Do NOT flag DockTileAppIconMode.automatic returning a concrete image name as a bug; it is the intentional Dock-side persistence path. Only when the user explicitly selects light or dark mode is the plugin's behavior identical to cmuxApp.swift. The .automatic branch must NOT return nil from the plugin.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2918-3021
Timestamp: 2026-04-14T20:00:12.468Z
Learning: Repo: manaflow-ai/cmux — Accessibility convention: For toolbar actions in macOS 26+ SwiftUI .toolbar (Sources/ContentView.swift), use localized .accessibilityLabel (and identifiers) for VoiceOver; AppKit NSToolbarItems in Sources/WindowToolbarController.swift set localized label/toolTip/accessibilityDescription. Label(...).labelStyle(.iconOnly) is not required here as VO picks up the localized accessibilityLabel.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: debgotwired
Repo: manaflow-ai/cmux PR: 1149
File: Sources/ContentView.swift:3977-3978
Timestamp: 2026-03-10T09:33:29.814Z
Learning: In manaflow-ai/cmux, for Sources/ContentView.swift, do not modify the sync-seeding behavior of scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:). It is shared infrastructure across all submenus. Treat brief initial flashes as consistent with existing submenus. Any UX improvement (for example making seeding synchronous on a forced corpus refresh) should not be implemented in feature-scoped PRs; instead implement and test such UX changes globally in a dedicated follow-up PR.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Comment thread Sources/ContentView.swift
Comment on lines +15598 to +15616
private func hideDividers() {
guard let window else { return }
patchSplitViews(in: window.contentView)
}

private func patchSplitViews(in view: NSView?) {
guard let view else { return }
if let splitView = view as? NSSplitView {
splitView.dividerStyle = .thin
// Clear the divider color via ObjC messaging (private API).
let selector = NSSelectorFromString("setDividerColor:")
if splitView.responds(to: selector) {
splitView.perform(selector, with: NSColor.clear)
}
}
for subview in view.subviews {
patchSplitViews(in: subview)
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Limit divider hiding to the outer navigation split view.

patchSplitViews(in: window.contentView) walks every NSSplitView in the window, not just the NavigationSplitView container. That will also clear dividers in nested split-based content, which is a much broader UI change than hiding the single sidebar divider.

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

In `@Sources/ContentView.swift` around lines 15598 - 15616, The current
patchSplitViews(in:) walks every NSSplitView in the window and clears dividers
globally; instead limit the change to the outer NavigationSplitView container:
update hideDividers() to locate only the top-level split view that represents
the navigation container (search window.contentView?.subviews for the
NSSplitView that is the NavigationSplitView host, e.g. the direct child
corresponding to the app's sidebar split) and call a new patch function only on
that single split view; keep patchSplitViews(in:) but rename/use it to operate
on a single NSSplitView (or add a patchNavigationSplitView(in:)) so nested
split-based content is not modified. Ensure you reference hideDividers(),
patchSplitViews(in:), NavigationSplitView and window.contentView when making the
change.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Noted — in practice the only NSSplitView in the window tree on macOS 26 is the NavigationSplitView's, so patchSplitViews has one target by construction. If we add another split view later we'll scope the walk to the NavigationSplitView ancestor.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@rodchristiansen Acknowledged — thanks for the context. Given macOS 26 only installs the NavigationSplitView’s NSSplitView in this window, the current recursive walk is effectively scoped to that single split and is fine as-is. No change requested. If helpful down the road, a tiny DEBUG-only check that logs when more than one NSSplitView is found could catch future regressions early, but we can leave this as-is for now.


✏️ Learnings added
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:15597-15615
Timestamp: 2026-04-14T20:05:50.303Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26 in this app, the only NSSplitView in the window’s view tree is the one owned by NavigationSplitView. Therefore SplitViewDividerHiderView.hideDividers()/patchSplitViews(in:) effectively scope to that single split. Do not flag “global divider clearing” for this path unless additional split views are introduced later.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2889-2911
Timestamp: 2026-04-14T19:59:52.026Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26, the NavigationSplitView sidebar width is synchronized back to model state via a GeometryReader that updates sidebarWidth and SidebarState.persistedWidth on width changes. Do not flag “write-only sidebar width” drift for this path going forward.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-04-14T20:01:09.378Z
Learning: Repo: manaflow-ai/cmux — macOS 26 UI: NavigationSplitView with SwiftUI .toolbar owns window chrome; WindowToolbarController is not used on macOS 26 and remains as an AppKit fallback. Notifications popover on macOS 26 is driven via AppDelegate.toggleNotificationsPopover posting cmux.toggleNotificationsPopover, which ContentView observes to present a SwiftUI popover.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to Sources/**/GhosttyTerminalView.swift : Mount `SurfaceSearchOverlay` from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`, to maintain proper layering during split/workspace churn

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:3586-3604
Timestamp: 2026-04-14T20:00:18.091Z
Learning: Repo: manaflow-ai/cmux — Sources/ContentView.swift — On macOS 26+, the app intentionally keeps NSWindow.StyleMask.fullSizeContentView enabled even with the native (non-transparent) system titlebar. The titlebar handles dragging natively; isMovable=true and isMovableByWindowBackground=false are used. Any comment suggesting “without fullSizeContentView” is outdated.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to Sources/**/ContentView.swift : In `TabItemView` in `ContentView.swift`, use `Equatable` conformance + `.equatable()` to skip body re-evaluation during typing. Do not add `EnvironmentObject`, `ObservedObject` (besides `tab`), or `Binding` properties without updating the `==` function. Do not remove `.equatable()` from the ForEach call site. Do not read `tabManager` or `notificationStore` in the body; use precomputed `let` parameters instead

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/ContentView.swift : In `TabItemView` in `ContentView.swift`: uses `Equatable` conformance + `.equatable()` to skip body re-evaluation during typing. Do not add `EnvironmentObject`, `ObservedObject` (besides `tab`), or `Binding` properties without updating the `==` function. Do not remove `.equatable()` from the ForEach call site. Do not read `tabManager` or `notificationStore` in the body; use precomputed `let` parameters instead.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T00:58:31.755Z
Learning: Applies to **/ContentView.swift : In `TabItemView` in `ContentView.swift`: uses `Equatable` conformance + `.equatable()` to skip body re-evaluation during typing. Do not add `EnvironmentObject`, `ObservedObject` (besides `tab`), or `Binding` properties without updating the `==` function. Do not remove `.equatable()` from the ForEach call site. Do not read `tabManager` or `notificationStore` in the body; use precomputed `let` parameters instead.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-05T03:20:48.079Z
Learning: Applies to **/*TerminalView.swift : Terminal find layering contract: SurfaceSearchOverlay must be mounted from GhosttySurfaceScrollView in Sources/GhosttyTerminalView.swift (AppKit portal layer), not from SwiftUI panel containers

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to **/*.swift : Use the Debug menu (macOS menu bar, DEBUG builds only) for visual iteration. To add a debug toggle or visual option: create an `NSWindowController` subclass with a `shared` singleton, add it to the 'Debug Windows' menu in `Sources/cmuxApp.swift`, and add a SwiftUI view with `AppStorage` bindings for live changes

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T02:19:07.331Z
Learning: Applies to Sources/**/TerminalWindowPortal.swift : In `WindowTerminalHostView.hitTest()` in `TerminalWindowPortal.swift`, keep all divider/sidebar/drag routing gated to pointer events only. Do not add work outside the `isPointerEvent` guard to avoid typing latency

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T04:48:00.216Z
Learning: Applies to **/TerminalWindowPortal.swift : In `WindowTerminalHostView.hitTest()` in `TerminalWindowPortal.swift`: all divider/sidebar/drag routing must be gated to pointer events only. Do not add work outside the `isPointerEvent` guard as this path is called on every event including keyboard input.

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/ContentView.swift:3194-3196
Timestamp: 2026-03-04T14:05:48.668Z
Learning: In manaflow-ai/cmux (PR `#819`), Sources/ContentView.swift: The command palette’s external window labels intentionally use the global window index from the full orderedSummaries (index + 1), matching the Window menu in AppDelegate. Do not reindex after filtering out the current window to avoid mismatches (“Window 2” for an external window is expected).

Learnt from: Znboston
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-03-26T19:00:12.434Z
Learning: Repo: manaflow-ai/cmux — Sources/TabManager.swift — In `moveWorkspaceOutOfGroup(_:toSidebarIndex:)`, do NOT call `syncUngroupedSidebarOrder()` after inserting into `sidebarOrder`. That helper derives order from the `tabs` array insertion index, which is unrelated to the user's drag-drop target position. `sidebarOrder` is the source of truth for sidebar rendering; the raw insertion at the resolved sidebar index is correct and sufficient. Calling `syncUngroupedSidebarOrder()` would overwrite the intended drop position with stale `tabs` order, causing a snap-back regression.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 2505
File: Sources/AppDelegate.swift:4910-4918
Timestamp: 2026-04-06T02:02:45.662Z
Learning: Repo: manaflow-ai/cmux — File: Sources/AppDelegate.swift — Keyboard repair pattern: In AppDelegate.repairFocusedTerminalKeyboardRoutingIfNeeded(window:event:), determine whether to repair focus by asking the focused terminal’s hosted view if the current first responder already matches that panel’s preferred keyboard target. Implemented via hostedView.responderMatchesPreferredKeyboardFocus(responder) inside responderNeedsFocusedTerminalKeyRepair(_:in:hostedView:). This covers same-window drift to a different Ghostty surface without comparing workspace/panel IDs. Verified by test cmuxTests/AppDelegateShortcutRoutingTests.swift::testWindowSendEventRepairsVisibleSameWindowResponderDriftForFocusedTerminalTyping.

Learnt from: MaTriXy
Repo: manaflow-ai/cmux PR: 1460
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-16T08:02:06.824Z
Learning: In Swift sources, for any panel_id-only route handling in v2PanelMarkBackground(params:) and v2PanelMarkForeground(params:), first attempt v2ResolveTabManager(params:). Use the manager only if it actually owns the panelId; otherwise fall back to AppDelegate.shared?.locateSurface(surfaceId:) to locate the correct TabManager across windows. Apply this pattern to all panel_id-only routes to avoid active-window bias.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/AppDelegate.swift:10168-10189
Timestamp: 2026-04-14T20:02:38.217Z
Learning: Repo manaflow-ai/cmux — On macOS 26, the notifications popover is owned by ContentView (State isNotificationsPopoverPresented). AppDelegate APIs (toggle/show/dismiss/isShown) must route via NotificationCenter with a window-scoped userInfo["windowId"] and track visibility per-window, not call UpdateTitlebarAccessoryController. ContentView should observe show/dismiss/toggle and post a visibilityDidChange notification {windowId, visible}.

Learnt from: wonbywondev
Repo: manaflow-ai/cmux PR: 1795
File: Sources/ContentView.swift:1392-1395
Timestamp: 2026-03-19T09:21:24.369Z
Learning: Repo manaflow-ai/cmux — Notifications UI: Production notifications are shown via NSPopover (toggleNotificationsPopover in Sources/Update/UpdateTitlebarAccessory.swift). The SidebarSelectionState.selection == .notifications path in Sources/ContentView.swift is test/scaffolding-only (set in AppDelegate for tests) and isn’t used by user actions; .tabs remains active in normal flows. Therefore, folder-drop handling doesn’t need to flip selection.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 1909
File: Sources/AppDelegate.swift:1955-1970
Timestamp: 2026-03-21T07:13:41.796Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.registerMainWindow ownership pattern: the primary window registers from ContentView.onAppear and passes the SwiftUI-owned FileBrowserDrawerState; secondary windows created via AppDelegate.createMainWindow construct and pass their own FileBrowserDrawerState. This mirrors SidebarState and avoids re-registration mismatches.

Learnt from: tayl0r
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-08T03:36:30.160Z
Learning: Repo: manaflow-ai/cmux — AppDelegate.FileBrowserDrawerState threading pattern (PR `#1909`, commit e0e57809): FileBrowserDrawerState must be threaded through AppDelegate.configure() as a weak stored property (matching the sidebarState pattern), passed through both configure() call sites, with registerMainWindow parameter made non-optional. The fallback `?? FileBrowserDrawerState()` must NOT be used as it creates detached instances that are not properly owned by the window context.

Learnt from: lucasward
Repo: manaflow-ai/cmux PR: 1903
File: Sources/ContentView.swift:13069-13113
Timestamp: 2026-03-21T06:23:38.764Z
Learning: Repo: manaflow-ai/cmux — In Sources/ContentView.swift, DraggableFolderNSView.updateIcon() intentionally applies the "sidebarMonochromeIcons" setting only on view creation (no live observer). Do not add defaults observers/AppStorage for live refresh in feature-scoped PRs; a live-refresh can be considered in a separate follow-up.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2575
File: Sources/cmuxApp.swift:0-0
Timestamp: 2026-04-06T09:01:51.979Z
Learning: Repo: manaflow-ai/cmux — File: Sources/cmuxApp.swift — SettingsView now uses SwiftUI `.searchable(text:placement:prompt:)` for the sidebar search, which provides a native (accessible) clear button. Do not flag missing accessibility on a custom clear button in this view going forward.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2884
File: Sources/AppIconDockTilePlugin.swift:16-19
Timestamp: 2026-04-14T08:50:27.729Z
Learning: Repo: manaflow-ai/cmux — Sources/AppIconDockTilePlugin.swift — Two-layer icon persistence contract (PR `#2884`):
- App process (Sources/cmuxApp.swift): AppIconMode.automatic returns nil for imageName to avoid writing custom icons to the bundle from the app side (runtime-only swaps).
- Dock plugin (Sources/AppIconDockTilePlugin.swift): DockTileAppIconMode.automatic intentionally returns a concrete NSImage.Name (AppIconDark or AppIconLight) based on the current effective appearance, so the plugin persists an appearance-matched icon to the bundle. This ensures the Dock shows the correct icon after force-kill/quit instead of falling back to the static bundle asset.
- Do NOT flag DockTileAppIconMode.automatic returning a concrete image name as a bug; it is the intentional Dock-side persistence path. Only when the user explicitly selects light or dark mode is the plugin's behavior identical to cmuxApp.swift. The .automatic branch must NOT return nil from the plugin.

Learnt from: rodchristiansen
Repo: manaflow-ai/cmux PR: 2647
File: Sources/ContentView.swift:2918-3021
Timestamp: 2026-04-14T20:00:12.468Z
Learning: Repo: manaflow-ai/cmux — Accessibility convention: For toolbar actions in macOS 26+ SwiftUI .toolbar (Sources/ContentView.swift), use localized .accessibilityLabel (and identifiers) for VoiceOver; AppKit NSToolbarItems in Sources/WindowToolbarController.swift set localized label/toolTip/accessibilityDescription. Label(...).labelStyle(.iconOnly) is not required here as VO picks up the localized accessibilityLabel.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2525
File: Sources/GhosttyTerminalView.swift:481-513
Timestamp: 2026-04-02T10:13:39.235Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift, terminal file-link resolution trims trailing unmatched closing delimiters “) ] } >” only when they are dangling (more closers than openers), preserving wrapped tokens like “(file:///tmp/a.png)”. Implemented via terminalFileLinkTrailingClosingDelimiters and count comparison inside trimTrailingTerminalFileLinkPunctuation(_:) and exercised by a regression test (PR `#2525`, commit 3f5c5b6d).

Learnt from: atani
Repo: manaflow-ai/cmux PR: 819
File: Sources/AppDelegate.swift:0-0
Timestamp: 2026-03-04T14:05:42.574Z
Learning: Guideline: In Swift files (cmux project), when handling pluralized strings, prefer using localization keys with the ICU-style plural forms .one and .other. For example, use keys like statusMenu.unreadCount.one for the singular case (1) and statusMenu.unreadCount.other for all other counts, and similarly for statusMenu.tooltip.unread.one/other. Rationale: ensures correct pluralization across locales and makes localization keys explicit. Review code to ensure any unread count strings and related tooltips follow this .one/.other key pattern and verify the correct value is chosen based on the count.

Learnt from: austinywang
Repo: manaflow-ai/cmux PR: 954
File: Sources/TerminalController.swift:0-0
Timestamp: 2026-03-05T22:04:34.712Z
Learning: Adopt the convention: for health/telemetry tri-state values in Swift, prefer Optionals (Bool?) over sentinel booleans. In TerminalController.swift, socketConnectable is Bool? and only set when socketProbePerformed is true; downstream logic must treat nil as 'not probed'. Ensure downstream code checks for nil before using a value and uses explicit non-nil checks to determine state, improving clarity and avoiding misinterpretation of default false.

Learnt from: moyashin63
Repo: manaflow-ai/cmux PR: 1074
File: Sources/AppDelegate.swift:7523-7545
Timestamp: 2026-03-09T01:38:24.337Z
Learning: When the command palette is visible (as in manaflow-ai/cmux Sources/AppDelegate.swift), ensure the shortcut handling consumes most Command shortcuts to protect the palette's text input. Specifically, do not allow UI zoom shortcuts (Cmd+Shift+= / Cmd+Shift+− / Cmd+Shift+0) to trigger while the palette is open. Do not reorder shortcut handlers (e.g., uiZoomShortcutAction(...)) to bypass this guard; users must close the palette before performing zoom actions. This guideline should apply to Swift source files handling global shortcuts within the app.

Learnt from: debgotwired
Repo: manaflow-ai/cmux PR: 1149
File: Sources/ContentView.swift:3977-3978
Timestamp: 2026-03-10T09:33:29.814Z
Learning: In manaflow-ai/cmux, for Sources/ContentView.swift, do not modify the sync-seeding behavior of scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh:). It is shared infrastructure across all submenus. Treat brief initial flashes as consistent with existing submenus. Any UX improvement (for example making seeding synchronous on a forced corpus refresh) should not be implemented in feature-scoped PRs; instead implement and test such UX changes globally in a dedicated follow-up PR.

Learnt from: zlatkoc
Repo: manaflow-ai/cmux PR: 1368
File: Sources/Panels/BrowserPanel.swift:69-69
Timestamp: 2026-03-13T13:46:01.733Z
Learning: Do not wrap engine/brand name literals (e.g., displayName values such as Google, DuckDuckGo, Bing, Kagi, Startpage) in String(localized: ...). These are brand/product names that are not translatable UI text. Localization should apply to generic UI strings (labels, buttons, error messages, etc.). Apply this guideline across Swift source files under Sources/ (notably in BrowserPanel.swift and similar UI/engine-related strings) and flag only brand-name strings that are part of user-facing UI text appropriately for translation scope.

Learnt from: kjb0787
Repo: manaflow-ai/cmux PR: 1461
File: Sources/GhosttyTerminalView.swift:5904-5905
Timestamp: 2026-03-15T19:22:32.330Z
Learning: In Swift files under the Sources directory that manage terminal/scroll behavior, ensure the following: when preserving scroll across workspace switches, save savedScrollRow only if the scrollbar offset is greater than 0 (indicating the user has scrolled up). On restore, call scroll_to_row only if savedScrollRow is non-nil; if it is nil, rely on synchronizeScrollView() to keep bottom-pinned sessions following new output. This pattern should be applied wherever GhosttyTerminalView-like views implement setVisibleInUI(_:) to maintain consistent user scroll state across workspace switches.

Learnt from: pratikpakhale
Repo: manaflow-ai/cmux PR: 2011
File: Resources/Localizable.xcstrings:15256-15368
Timestamp: 2026-03-23T21:39:50.795Z
Learning: When reviewing this repo’s Swift localization usage, do not flag missing `String.localizedStringWithFormat` for calls that use the modern overload `String(localized: "key", defaultValue: "...\(variable)")` (where `defaultValue` is a `String.LocalizationValue` built with `\(…)`). That overload natively supports interpolation and the xcstrings/runtime substitution handles the resulting placeholders automatically. Only require `String.localizedStringWithFormat` when using the older `String(localized:)` overload that takes a plain `String` (i.e., where format arguments must be passed separately), such as for keys like `clipboard.sshError.single`.

Learnt from: thunter009
Repo: manaflow-ai/cmux PR: 1825
File: Sources/TerminalController.swift:3620-3622
Timestamp: 2026-03-25T00:32:54.735Z
Learning: When validating or reporting workspace/tab colors in this repo, only accept and use 6-digit hex colors in the form `#RRGGBB` (no alpha, i.e., do not allow `#RRGGBBAA`). Ensure validation logic matches the existing behavior (e.g., WorkspaceTabColorSettings.normalizedHex(...) and TabManager.setTabColor(tabId:color:) as well as CLI/cmux.swift). Update any error/help text for workspace color to reference only `#RRGGBB` (not `#RRGGBBAA`).

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 2545
File: Sources/GhosttyTerminalView.swift:3891-3903
Timestamp: 2026-04-02T21:37:21.463Z
Learning: In Swift source files like Sources/GhosttyTerminalView.swift, avoid logging raw startup commands or initialInput even in DEBUG (to prevent leaking sensitive paths/tokens and multiline content). If you need to diagnose startup/input, log only non-sensitive metadata such as (1) presence flags (e.g., hasStartupCommand/hasInitialInput), (2) byte counts, and (3) the relevant surface id (so issues can be correlated without exposing the underlying strings).

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2528
File: Sources/cmuxApp.swift:6439-6444
Timestamp: 2026-04-03T03:35:54.082Z
Learning: In this repo’s keyboard shortcut implementation, ensure `KeyboardShortcutSettings.setShortcut(...)` does nothing (no-op) when `KeyboardShortcutSettings.isManagedBySettingsFile(action)` returns `true` (i.e., the shortcut is managed via `settings.json`). This prevents writing back overrides into `UserDefaults` and keeps `settings.json` as the source of truth.

Comment thread Sources/ContentView.swift Outdated
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.
@rodchristiansen
Copy link
Copy Markdown
Author

Rebased onto current manaflow-ai/main (tip 03a0c14b) and squashed to a single commit to keep the diff clean against the "Split AppDelegate support code" refactor.

Reconciliation notes:

  • Sources/cmuxApp.swift: kept the mainWindowContent extraction; merged in upstream's new .environmentObject(fileExplorerState) and appDelegate.fileExplorerState = fileExplorerState. applyIcon keeps both upstream's isApplicationFinishedLaunching guard and the macOS 26 early return.
  • Sources/ContentView.swift: upstream moved SidebarBackdrop into a helper layer, so the old conditional .background modifier is no longer needed on this path.
  • Sources/Workspace.swift: combined upstream's new tabTitleFontSize parameter with tabBarHeight/showSplitButtons (argument order fixed).
  • project.pbxproj: re-applied the AppIcon.icon wiring (PBXBuildFile entry, folder.iconcomposer.icon type, Resources phase) on top of upstream's current state.
  • vendor/bonsplit: pinned at upstream SHA. The single-tab tab-bar hiding needs the matching bonsplit-side change (PaneContainerView conditional on tabBarHeight > 0 || tabs.count > 1). Happy to open that as a separate bonsplit PR — without it, the tab bar just renders at 0 height on macOS 26 rather than being truly hidden.

Local compile (xcodebuild … Debug) passes on macOS 26. Net diff +605 / -134 across 10 files, unchanged in scope vs the prior tip.

Comment thread Sources/Workspace.swift
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.
Comment thread Sources/ContentView.swift
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread Sources/GhosttyTerminalView.swift
Comment thread Sources/AppDelegate.swift
Comment thread Sources/AppDelegate.swift
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/TerminalWindowPortal.swift Outdated
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.
Comment thread Sources/ContentView.swift
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.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread .gitmodules
[submodule "vendor/bonsplit"]
path = vendor/bonsplit
url = https://github.com/manaflow-ai/bonsplit.git
url = https://github.com/rodchristiansen/bonsplit.git
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c89a423. Configure here.

Comment thread Sources/WindowToolbarController.swift
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread Sources/Workspace.swift
@@ -6752,8 +6752,7 @@ final class Workspace: Identifiable, ObservableObject {
private static func bonsplitAppearance(from config: GhosttyConfig) -> BonsplitConfiguration.Appearance {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 24, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

Comment thread .gitmodules
[submodule "vendor/bonsplit"]
path = vendor/bonsplit
url = https://github.com/manaflow-ai/bonsplit.git
url = https://github.com/rodchristiansen/bonsplit.git
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 24, 2026

Choose a reason for hiding this comment

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

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>
Suggested change
url = https://github.com/rodchristiansen/bonsplit.git
url = https://github.com/manaflow-ai/bonsplit.git
Fix with Cubic

Two follow-ups after on-device verification of c89a423:

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 9db1c3a (system-toggle stripper + custom leading toggle)
and 894ec52 (re-introduction of the stripper after the prior trial of
this approach), now that the sidebar-internal trial of dcffaf9/58cbc7b8
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).
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.

2 participants