Skip to content

Raise all cmux windows on auth callback, Settings on top - #3044

Closed
lawrencecchen wants to merge 4 commits into
mainfrom
fix-auth-callback-focus
Closed

lawrencecchen wants to merge 4 commits into
mainfrom
fix-auth-callback-focus

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

After completing the Stack Auth sign-in and clicking "Return to cmux" on the browser handoff page, only the Settings window got focused. Any workspace windows stayed buried behind whatever app was in front before — the flow surfaced the Settings row UI update, but the rest of cmux was lost.

AppDelegate.application(_:open:) now calls focusAppAfterAuthCallback() once handleCallbackURL resolves:

  1. NSApp.activate(ignoringOtherApps: true) brings cmux to the foreground.
  2. Iterate NSApp.orderedWindows.reversed(), calling orderFront(nil) on each visible non-Settings window so their relative z-order is preserved.
  3. makeKeyAndOrderFront(nil) on the Settings window so the just-signed-in row lands on top.

Test plan

  • Build is green locally.
  • Sign out, open multiple workspace windows (order A front, B behind), open Settings, click Sign In…, complete in the browser, click Return to cmux. Workspace A should be in front, B behind, Settings above everything.

Summary by cubic

Synchronously activate cmux on the Stack Auth callback, then restore all windows with workspace order preserved and Settings on top. Fixes cases where only Settings came forward and other windows stayed behind other apps.

  • Bug Fixes
    • Activate inside application(_:open:) using NSApp.activate() and NSRunningApplication.current.activate(options: [.activateAllWindows]) so macOS 14+ grants focus.
    • After handleCallbackURL, re-order visible windows back-to-front (makeKeyAndOrderFront first workspace, orderFrontRegardless others) and make Settings key and front.

Written for commit 249c4a6. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved app focus and window ordering after completing browser-based authentication. The app now immediately regains focus before handling callbacks, reactivates and restores visible windows after each callback, preserves their relative order, and ensures the Settings window is brought forward and made key when visible for a smoother post-authentication experience.

Previously application(_:open:) only routed the cmux://auth-callback
URL to AuthManager.handleCallbackURL. That flipped the Settings
row's UI state but left the rest of the app buried — the browser
remained frontmost, and any workspace windows stayed behind whatever
app the user was in before clicking "Return to cmux".

After handleCallbackURL resolves we now:
1. NSApp.activate(ignoringOtherApps: true) to bring cmux to the
   foreground.
2. Iterate NSApp.orderedWindows.reversed(), calling orderFront on
   each non-Settings visible window so their relative z-order is
   preserved (back windows stay behind front windows).
3. makeKeyAndOrderFront on the Settings window so the
   just-signed-in UI lands on top.
@vercel

vercel Bot commented Apr 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 20, 2026 10:04pm

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 1 file

@coderabbitai

coderabbitai Bot commented Apr 20, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

AppDelegate.application(_:open:) now activates the app synchronously before processing auth-callback URLs, invokes AuthManager.shared.handleCallbackURL(url) for each URL, and after each callback completion re-activates the app and reorders visible windows, ensuring Settings is brought to key/front if visible. (34 words)

Changes

Cohort / File(s) Summary
App Window Focus & Auth Callback Handling
Sources/AppDelegate.swift
Adds synchronous pre-processing activation via focusAppForAuthCallback(), iterates auth-callback URLs and calls AuthManager.shared.handleCallbackURL(url) (async), and after each completion calls raiseWindowsAfterAuthCallback() to re-activate and reorder visible windows (reverse z-order, bring workspace windows forward, then make Settings key/front if visible).

Sequence Diagram(s)

sequenceDiagram
    participant System as System (open URL)
    participant App as AppDelegate
    participant Auth as AuthManager
    participant Win as WindowManager (NSApp / NSRunningApplication)

    System->>App: application(_:open: URLs)
    App->>Win: focusAppForAuthCallback()\n(NSApp.activate + current.activate)
    loop for each auth-callback URL
        App->>Auth: handleCallbackURL(url) (async)
        Auth-->>App: completion (success/failure)
        App->>Win: raiseWindowsAfterAuthCallback()\n(activate + reorder visible windows, bring Settings key)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I nudged the app when the browser called,
I hopped it forward so no view stalled,
Each callback finished, I raised windows bright,
Settings hopped last to claim the light. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% 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 accurately captures the main change: restoring all cmux windows during auth callback with Settings positioned on top.
Description check ✅ Passed The description covers the Summary section well but the Test plan is incomplete with only the build passing verified; the critical manual test case remains unchecked.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-auth-callback-focus

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

greptile-apps Bot commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a window-focus regression after the Stack Auth sign-in callback: application(_:open:) now calls focusAppAfterAuthCallback() once handleCallbackURL resolves, which activates the app, re-raises all visible non-Settings windows in their original z-order, and then puts the Settings window on top.

The window-ordering logic is correct — snapshotting orderedWindows before the loop and iterating back-to-front with orderFront preserves relative z-order, and the settingsWindow identity guard prevents it from being double-raised.

Confidence Score: 5/5

Safe to merge — logic is correct, all findings are P2 style suggestions.

The window z-order algorithm is sound, the Settings window nil/invisible cases are handled correctly, and @mainactor ensures no concurrency issues. The two P2 notes (style inconsistency with NSApplication.shared vs NSApp, and focus-on-error behavior) don't block correctness.

No files require special attention.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds focusAppAfterAuthCallback() that activates the app, re-raises non-Settings windows in preserved z-order, then brings Settings to front; called from the auth URL handler after handleCallbackURL resolves (including on error).

Sequence Diagram

sequenceDiagram
    participant Browser
    participant AppDelegate
    participant AuthManager
    participant NSApp

    Browser->>AppDelegate: application(_:open:) cmux://auth-callback
    AppDelegate->>AppDelegate: filter authCallbacks
    AppDelegate->>AuthManager: handleCallbackURL(url) [async]
    AuthManager-->>AppDelegate: success / throws
    AppDelegate->>AppDelegate: focusAppAfterAuthCallback()
    AppDelegate->>NSApp: activate(ignoringOtherApps: true)
    loop visible.reversed() excluding settingsWindow
        AppDelegate->>NSApp: orderFront(nil) — workspace windows (back to front)
    end
    AppDelegate->>NSApp: makeKeyAndOrderFront(nil) — Settings window
Loading

Comments Outside Diff (1)

  1. Sources/AppDelegate.swift, line 2526-2533 (link)

    P2 Window focus triggered on auth failure too

    focusAppAfterAuthCallback() sits after the do/catch block, so it fires whether handleCallbackURL throws or not. When auth fails, cmux still forcefully activates and raises all windows. Given the user deliberately clicked "Return to cmux" in the browser this is probably fine, but if silent failures are possible it could feel jarring. Consider gating the call on success if that's not the intent:

Reviews (1): Last reviewed commit: "macos auth: raise all cmux windows on si..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift Outdated
// cmux workspace windows buried behind other apps. Activate the
// app, re-front every visible cmux window in back-to-front order
// to preserve relative z-order, then raise Settings on top.
NSApplication.shared.activate(ignoringOtherApps: true)

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 NSApplication.shared vs NSApp style inconsistency

The rest of the file (lines 3585 and 7258) uses NSApp.activate(ignoringOtherApps: true); this new call uses NSApplication.shared. They're equivalent, but the inconsistency is minor noise.

Suggested change
NSApplication.shared.activate(ignoringOtherApps: true)
NSApp.activate(ignoringOtherApps: true)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2cb77c1cdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/AppDelegate.swift Outdated
} catch {
NSLog("auth.callback failed: %@", "\(error)")
}
self.focusAppAfterAuthCallback()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate auth-callback window raising on successful callback

Call to focusAppAfterAuthCallback() currently runs even when handleCallbackURL throws, so expired/invalid cmux://auth-callback links still activate the app and raise all windows. In practice this creates unexpected focus stealing for failed sign-in attempts (or any stray callback URL) and is not tied to a successful auth state update; moving the focus call into the success path avoids that regression.

Useful? React with 👍 / 👎.

Previous attempt still left workspace windows buried because
NSApp.activate(ignoringOtherApps:) is cooperative on macOS 14+ and
only guarantees the key window comes forward. Switched to
NSRunningApplication.current.activate(options: [.activateAllWindows])
which is the supported API for "bring every window of this app
along," and upgraded the per-window call to orderFrontRegardless so
the re-ordering takes effect even if cooperative activation hasn't
completed yet.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 6783-6799: Add DEBUG-only dlog() calls in
focusAppAfterAuthCallback() to trace the focus-repair sequence: wrap logs in `#if`
DEBUG / `#endif` and emit messages before/after
NSApplication.shared.activate(ignoringOtherApps:), log the list of visible
windows (from visible variable) and each window being ordered via
window.orderFront(nil), and log when SettingsWindowController.shared.window is
made key/front; use clear messages referencing the function name and window
identities to aid debugging.
🪄 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: da7a47dd-8b5f-439d-a86e-1386f1b693ea

📥 Commits

Reviewing files that changed from the base of the PR and between fd93a7c and 2cb77c1.

📒 Files selected for processing (1)
  • Sources/AppDelegate.swift

Comment thread Sources/AppDelegate.swift Outdated
Comment on lines +6783 to +6799
private func focusAppAfterAuthCallback() {
// When the sign-in deeplink returns through the browser only the
// Settings window gets re-fronted by default, leaving any regular
// cmux workspace windows buried behind other apps. Activate the
// app, re-front every visible cmux window in back-to-front order
// to preserve relative z-order, then raise Settings on top.
NSApplication.shared.activate(ignoringOtherApps: true)

let settingsWindow = SettingsWindowController.shared.window
let visible = NSApp.orderedWindows.filter { $0.isVisible }
for window in visible.reversed() where window !== settingsWindow {
window.orderFront(nil)
}
if let settingsWindow, settingsWindow.isVisible {
settingsWindow.makeKeyAndOrderFront(nil)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add DEBUG logging for the auth focus repair path.

This helper mutates app/window focus but doesn’t emit a focus debug event, which makes auth-callback focus regressions harder to diagnose.

Proposed logging addition
 private func focusAppAfterAuthCallback() {
+#if DEBUG
+    let beforeKey = NSApp.keyWindow?.windowNumber ?? -1
+    let beforeMain = NSApp.mainWindow?.windowNumber ?? -1
+    dlog(
+        "auth.callback.focus.begin keyWin=\(beforeKey) mainWin=\(beforeMain) " +
+        "ordered=\(NSApp.orderedWindows.map { "\($0.windowNumber):\($0.identifier?.rawValue ?? "nil"):\($0.isVisible ? 1 : 0)" }.joined(separator: ","))"
+    )
+#endif
     // When the sign-in deeplink returns through the browser only the
     // Settings window gets re-fronted by default, leaving any regular
     // cmux workspace windows buried behind other apps. Activate the
     // app, re-front every visible cmux window in back-to-front order
     // to preserve relative z-order, then raise Settings on top.
     NSApplication.shared.activate(ignoringOtherApps: true)
 
     let settingsWindow = SettingsWindowController.shared.window
     let visible = NSApp.orderedWindows.filter { $0.isVisible }
     for window in visible.reversed() where window !== settingsWindow {
         window.orderFront(nil)
     }
     if let settingsWindow, settingsWindow.isVisible {
         settingsWindow.makeKeyAndOrderFront(nil)
     }
+#if DEBUG
+    dlog(
+        "auth.callback.focus.end keyWin=\(NSApp.keyWindow?.windowNumber ?? -1) " +
+        "mainWin=\(NSApp.mainWindow?.windowNumber ?? -1) settingsVisible=\((settingsWindow?.isVisible == true) ? 1 : 0)"
+    )
+#endif
 }

As per coding guidelines, “All debug events (keys, mouse, focus, splits, tabs) must be logged to the unified debug log using the dlog() function and wrapped in #if DEBUG / #endif preprocessor directives.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private func focusAppAfterAuthCallback() {
// When the sign-in deeplink returns through the browser only the
// Settings window gets re-fronted by default, leaving any regular
// cmux workspace windows buried behind other apps. Activate the
// app, re-front every visible cmux window in back-to-front order
// to preserve relative z-order, then raise Settings on top.
NSApplication.shared.activate(ignoringOtherApps: true)
let settingsWindow = SettingsWindowController.shared.window
let visible = NSApp.orderedWindows.filter { $0.isVisible }
for window in visible.reversed() where window !== settingsWindow {
window.orderFront(nil)
}
if let settingsWindow, settingsWindow.isVisible {
settingsWindow.makeKeyAndOrderFront(nil)
}
}
private func focusAppAfterAuthCallback() {
`#if` DEBUG
let beforeKey = NSApp.keyWindow?.windowNumber ?? -1
let beforeMain = NSApp.mainWindow?.windowNumber ?? -1
dlog(
"auth.callback.focus.begin keyWin=\(beforeKey) mainWin=\(beforeMain) " +
"ordered=\(NSApp.orderedWindows.map { "\($0.windowNumber):\($0.identifier?.rawValue ?? "nil"):\($0.isVisible ? 1 : 0)" }.joined(separator: ","))"
)
`#endif`
// When the sign-in deeplink returns through the browser only the
// Settings window gets re-fronted by default, leaving any regular
// cmux workspace windows buried behind other apps. Activate the
// app, re-front every visible cmux window in back-to-front order
// to preserve relative z-order, then raise Settings on top.
NSApplication.shared.activate(ignoringOtherApps: true)
let settingsWindow = SettingsWindowController.shared.window
let visible = NSApp.orderedWindows.filter { $0.isVisible }
for window in visible.reversed() where window !== settingsWindow {
window.orderFront(nil)
}
if let settingsWindow, settingsWindow.isVisible {
settingsWindow.makeKeyAndOrderFront(nil)
}
`#if` DEBUG
dlog(
"auth.callback.focus.end keyWin=\(NSApp.keyWindow?.windowNumber ?? -1) " +
"mainWin=\(NSApp.mainWindow?.windowNumber ?? -1) settingsVisible=\((settingsWindow?.isVisible == true) ? 1 : 0)"
)
`#endif`
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 6783 - 6799, Add DEBUG-only dlog()
calls in focusAppAfterAuthCallback() to trace the focus-repair sequence: wrap
logs in `#if` DEBUG / `#endif` and emit messages before/after
NSApplication.shared.activate(ignoringOtherApps:), log the list of visible
windows (from visible variable) and each window being ordered via
window.orderFront(nil), and log when SettingsWindowController.shared.window is
made key/front; use clear messages referencing the function name and window
identities to aid debugging.

My Bash-driven repro surfaced the actual reason Settings came up
alone. On macOS 14+, NSApp.activate() and NSRunningApplication.
current.activate(options:) are cooperative: the OS only grants
them if the call fires synchronously while the user-event context
is still active. The original code routed activation through a
Task { @mainactor in ... }, which hops to a later run loop tick
and loses that context, so activation silently no-op'd and only
the subsequent makeKeyAndOrderFront on the Settings window
managed to pull cmux to the foreground.

Split the handler into two phases:

- focusAppForAuthCallback() fires synchronously inside
  application(_:open:), within the URL-event frame, so activation
  is granted.
- raiseWindowsAfterAuthCallback() runs after the async token
  exchange and re-orders workspace windows back-to-front with
  makeKeyAndOrderFront on the first (forces activation as a
  secondary guarantee) + orderFrontRegardless on the rest, then
  makeKeyAndOrderFront on Settings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

6793-6824: ⚠️ Potential issue | 🟡 Minor

Add DEBUG logging around the auth focus repair path.

These helpers mutate app/window focus but still don’t emit focus diagnostics, making regressions in this path hard to debug.

🔎 Proposed logging addition
     private func focusAppForAuthCallback() {
+#if DEBUG
+        dlog(
+            "auth.callback.focus.sync.begin " +
+            "key={\(debugWindowToken(NSApp.keyWindow))} main={\(debugWindowToken(NSApp.mainWindow))}"
+        )
+#endif
         NSApp.activate()
         NSRunningApplication.current.activate(options: [.activateAllWindows])
+#if DEBUG
+        dlog(
+            "auth.callback.focus.sync.end active=\(NSApp.isActive ? 1 : 0) " +
+            "key={\(debugWindowToken(NSApp.keyWindow))} main={\(debugWindowToken(NSApp.mainWindow))}"
+        )
+#endif
     }
@@
     private func raiseWindowsAfterAuthCallback() {
+#if DEBUG
+        dlog(
+            "auth.callback.raise.begin " +
+            "key={\(debugWindowToken(NSApp.keyWindow))} main={\(debugWindowToken(NSApp.mainWindow))}"
+        )
+#endif
         // Second activate to catch the case where the activation request
@@
         let settingsWindow = SettingsWindowController.shared.window
         let visible = NSApp.orderedWindows.filter { $0.isVisible }
+#if DEBUG
+        dlog(
+            "auth.callback.raise.visible " +
+            visible.map { "{\(debugWindowToken($0))}" }.joined(separator: " ")
+        )
+#endif
@@
         let workspaceWindows = visible.reversed().filter { $0 !== settingsWindow }
         for (idx, window) in workspaceWindows.enumerated() {
+#if DEBUG
+            dlog("auth.callback.raise.workspace idx=\(idx) window={\(debugWindowToken(window))}")
+#endif
             if idx == 0 {
                 window.makeKeyAndOrderFront(nil)
             } else {
                 window.orderFrontRegardless()
             }
         }
         if let settingsWindow, settingsWindow.isVisible {
+#if DEBUG
+            dlog("auth.callback.raise.settings window={\(debugWindowToken(settingsWindow))}")
+#endif
             settingsWindow.makeKeyAndOrderFront(nil)
         }
+#if DEBUG
+        dlog(
+            "auth.callback.raise.end active=\(NSApp.isActive ? 1 : 0) " +
+            "key={\(debugWindowToken(NSApp.keyWindow))} main={\(debugWindowToken(NSApp.mainWindow))}"
+        )
+#endif
     }

As per coding guidelines, “All debug events (keys, mouse, focus, splits, tabs) must be logged to the unified debug log using the dlog() function and wrapped in #if DEBUG / #endif preprocessor directives.”

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

In `@Sources/AppDelegate.swift` around lines 6793 - 6824, Add debug logging around
the auth focus repair path: inside focusAppForAuthCallback() and at the start
and key steps of raiseWindowsAfterAuthCallback() (before/after the
NSApp.activate()/NSRunningApplication.current.activate calls, before iterating
workspaceWindows and when making the first window key, and before/after bringing
settingsWindow forward) call dlog() with concise context messages; wrap all
dlog() calls in `#if` DEBUG / `#endif` to comply with guidelines and reference
SettingsWindowController.shared.window when logging which window is being
focused. Ensure messages identify the method name and the window (or lack
thereof) so focus transitions can be traced in the unified debug log.
🤖 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 6813-6822: The current logic reverses NSApp.orderedWindows then
treats the first element as the frontmost, making the originally backmost window
key; change the selection/ordering so the original frontmost becomes key.
Specifically, adjust how workspaceWindows is built (stop using
visible.reversed() or instead pick the last element as the key) so that in the
loop over workspaceWindows the element handled when idx == 0 is the original
frontmost window; keep the rest using orderFrontRegardless(), and continue to
skip settingsWindow (refer to visible, workspaceWindows, settingsWindow,
makeKeyAndOrderFront, orderFrontRegardless).

---

Duplicate comments:
In `@Sources/AppDelegate.swift`:
- Around line 6793-6824: Add debug logging around the auth focus repair path:
inside focusAppForAuthCallback() and at the start and key steps of
raiseWindowsAfterAuthCallback() (before/after the
NSApp.activate()/NSRunningApplication.current.activate calls, before iterating
workspaceWindows and when making the first window key, and before/after bringing
settingsWindow forward) call dlog() with concise context messages; wrap all
dlog() calls in `#if` DEBUG / `#endif` to comply with guidelines and reference
SettingsWindowController.shared.window when logging which window is being
focused. Ensure messages identify the method name and the window (or lack
thereof) so focus transitions can be traced in the unified debug log.
🪄 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: 3d83e8fe-16dc-450c-82c1-dcee0fb3a493

📥 Commits

Reviewing files that changed from the base of the PR and between 75c83c3 and 8e2cee4.

📒 Files selected for processing (1)
  • Sources/AppDelegate.swift

Comment thread Sources/AppDelegate.swift
Comment on lines +6813 to +6822
let workspaceWindows = visible.reversed().filter { $0 !== settingsWindow }
for (idx, window) in workspaceWindows.enumerated() {
if idx == 0 {
window.makeKeyAndOrderFront(nil)
} else {
window.orderFrontRegardless()
}
}
if let settingsWindow, settingsWindow.isVisible {
settingsWindow.makeKeyAndOrderFront(nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd Sources && sed -n '6800,6835p' AppDelegate.swift

Repository: manaflow-ai/cmux

Length of output: 1726


🏁 Script executed:

rg "didBecomeKeyNotification" --context 5 -A 5

Repository: manaflow-ai/cmux

Length of output: 3840


🏁 Script executed:

rg "let visible" Sources/AppDelegate.swift -B 3 -A 3

Repository: manaflow-ai/cmux

Length of output: 2980


Make the originally frontmost workspace key, not the backmost one.

NSApp.orderedWindows returns windows front-to-back. After visible.reversed(), the loop's first iteration (idx == 0) makes workspaceWindows[0]—the originally backmost window—the key window via makeKeyAndOrderFront(). The didBecomeKeyNotification observer then calls setActiveMainWindow(), leaving the app context pointing to the wrong workspace. Later orderFrontRegardless() calls restore visual z-order but don't fix the active context state.

🐛 Proposed fix
         let workspaceWindows = visible.reversed().filter { $0 !== settingsWindow }
-        for (idx, window) in workspaceWindows.enumerated() {
-            if idx == 0 {
-                window.makeKeyAndOrderFront(nil)
-            } else {
-                window.orderFrontRegardless()
-            }
+        for window in workspaceWindows {
+            window.orderFrontRegardless()
+        }
+        if let frontmostWorkspaceWindow = workspaceWindows.last {
+            frontmostWorkspaceWindow.makeKeyAndOrderFront(nil)
         }
         if let settingsWindow, settingsWindow.isVisible {
             settingsWindow.makeKeyAndOrderFront(nil)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let workspaceWindows = visible.reversed().filter { $0 !== settingsWindow }
for (idx, window) in workspaceWindows.enumerated() {
if idx == 0 {
window.makeKeyAndOrderFront(nil)
} else {
window.orderFrontRegardless()
}
}
if let settingsWindow, settingsWindow.isVisible {
settingsWindow.makeKeyAndOrderFront(nil)
let workspaceWindows = visible.reversed().filter { $0 !== settingsWindow }
for window in workspaceWindows {
window.orderFrontRegardless()
}
if let frontmostWorkspaceWindow = workspaceWindows.last {
frontmostWorkspaceWindow.makeKeyAndOrderFront(nil)
}
if let settingsWindow, settingsWindow.isVisible {
settingsWindow.makeKeyAndOrderFront(nil)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 6813 - 6822, The current logic
reverses NSApp.orderedWindows then treats the first element as the frontmost,
making the originally backmost window key; change the selection/ordering so the
original frontmost becomes key. Specifically, adjust how workspaceWindows is
built (stop using visible.reversed() or instead pick the last element as the
key) so that in the loop over workspaceWindows the element handled when idx == 0
is the original frontmost window; keep the rest using orderFrontRegardless(),
and continue to skip settingsWindow (refer to visible, workspaceWindows,
settingsWindow, makeKeyAndOrderFront, orderFrontRegardless).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

6806-6816: ⚠️ Potential issue | 🟠 Major

Keep the originally frontmost workspace as the active context.

visible.reversed() makes idx == 0 the originally backmost workspace, so Line 6810 can make the wrong main window key and update AppDelegate.tabManager to that workspace before Settings is raised. Prefer ordering all workspaces, then make the original frontmost workspace key before raising Settings. This was already flagged in an earlier review.

🐛 Proposed fix
         let settingsWindow = SettingsWindowController.shared.window
         let visible = NSApp.orderedWindows.filter { $0.isVisible }
         let workspaceWindows = visible.reversed().filter { $0 !== settingsWindow }
-        for (idx, window) in workspaceWindows.enumerated() {
-            if idx == 0 {
-                window.makeKeyAndOrderFront(nil)
-            } else {
-                window.orderFrontRegardless()
-            }
+        for window in workspaceWindows {
+            window.orderFrontRegardless()
+        }
+        if let frontmostWorkspaceWindow = workspaceWindows.last {
+            frontmostWorkspaceWindow.makeKeyAndOrderFront(nil)
         }
         if let settingsWindow, settingsWindow.isVisible {
             settingsWindow.makeKeyAndOrderFront(nil)
         }

Verify the AppKit ordering assumption if needed:

What order does NSApplication.orderedWindows return in AppKit, and does NSWindow.makeKeyAndOrderFront trigger didBecomeKey notifications?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 6806 - 6816, The current logic
reverses NSApp.orderedWindows so idx==0 becomes the original backmost window,
causing the wrong workspace to be made key and assigned to
AppDelegate.tabManager; change the sequence in the block handling
visible/workspaceWindows so you do not reverse the visible array: build
workspaceWindows from visible.filter { $0 !== settingsWindow } in original
order, call orderFrontRegardless() on all but the original frontmost, then
explicitly call makeKeyAndOrderFront(nil) on the original frontmost window (and
update AppDelegate.tabManager after that) before handling
settingsWindow.makeKeyAndOrderFront(nil); reference the visible,
workspaceWindows, settingsWindow variables and the
makeKeyAndOrderFront/orderFrontRegardless calls when implementing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/AppDelegate.swift`:
- Around line 6806-6816: The current logic reverses NSApp.orderedWindows so
idx==0 becomes the original backmost window, causing the wrong workspace to be
made key and assigned to AppDelegate.tabManager; change the sequence in the
block handling visible/workspaceWindows so you do not reverse the visible array:
build workspaceWindows from visible.filter { $0 !== settingsWindow } in original
order, call orderFrontRegardless() on all but the original frontmost, then
explicitly call makeKeyAndOrderFront(nil) on the original frontmost window (and
update AppDelegate.tabManager after that) before handling
settingsWindow.makeKeyAndOrderFront(nil); reference the visible,
workspaceWindows, settingsWindow variables and the
makeKeyAndOrderFront/orderFrontRegardless calls when implementing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a80dce3-c5de-4514-97b1-6528dcf3db72

📥 Commits

Reviewing files that changed from the base of the PR and between 8e2cee4 and 249c4a6.

📒 Files selected for processing (1)
  • Sources/AppDelegate.swift

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

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/AppDelegate.swift">

<violation number="1" location="Sources/AppDelegate.swift:6812">
P2: Using `orderFrontRegardless()` here can leave the top workspace window non-key when Settings is not visible, causing keyboard focus to remain on a different window.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/AppDelegate.swift
if idx == 0 {
window.makeKeyAndOrderFront(nil)
} else {
window.orderFrontRegardless()

@cubic-dev-ai cubic-dev-ai Bot Apr 20, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Using orderFrontRegardless() here can leave the top workspace window non-key when Settings is not visible, causing keyboard focus to remain on a different window.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/AppDelegate.swift, line 6812:

<comment>Using `orderFrontRegardless()` here can leave the top workspace window non-key when Settings is not visible, causing keyboard focus to remain on a different window.</comment>

<file context>
@@ -6780,18 +6788,29 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
+            if idx == 0 {
+                window.makeKeyAndOrderFront(nil)
+            } else {
+                window.orderFrontRegardless()
+            }
         }
</file context>
Suggested change
window.orderFrontRegardless()
window.makeKeyAndOrderFront(nil)
Fix with Cubic

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview — 249c4a6e Deployed Apr 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants