Skip to content

Fix Return on Cmd+Ctrl+W close confirmation - #1279

Merged
lawrencecchen merged 4 commits into
mainfrom
task-close-dialog-enter-focus
Mar 12, 2026
Merged

lawrencecchen merged 4 commits into
mainfrom
task-close-dialog-enter-focus

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a regression XCUITest that verifies Return confirms the Cmd+Ctrl+W close-window dialog
  • keep the close-window alert on the standard Return default-button path and explicitly focus the close button when the alert opens

Testing

Task

  • after Cmd+Ctrl+W we fail to focus on the dialog so pressing Enter doesn't work

Summary by cubic

Fixes a regression where pressing Return didn’t confirm the close-window dialog after Cmd+Ctrl+W. The Close button is now focused so Return works, and a UI test verifies the alert dismisses and the window closes.

  • Bug Fixes
    • Use the NSAlert default button path: set Close as the default and initial first responder, then make it first responder on open (removed custom Cmd+D shortcut).
    • Added testReturnConfirmsCloseWindowDialog XCUITest with helpers to wait for the alert to dismiss and the main window to close.

Written for commit 529008c. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved close-window confirmation dialog: keyboard focus is now set correctly, the close button is focused before presentation, and pressing Return confirms closing without altering shortcut behavior.
  • Tests
    • Added UI tests that verify pressing Return confirms the dialog and the main window closes.

@vercel

vercel Bot commented Mar 12, 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 Mar 12, 2026 0:16am

@coderabbitai

coderabbitai Bot commented Mar 12, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ccaacd90-1b2b-4c62-bcee-63a18ca8b439

📥 Commits

Reviewing files that changed from the base of the PR and between 39e8afb and 529008c.

📒 Files selected for processing (2)
  • Sources/AppDelegate.swift
  • cmuxUITests/CloseWindowConfirmDialogUITests.swift

📝 Walkthrough

Walkthrough

The AppDelegate close-window confirmation flow now sets the alert's default button and focuses the alert/window without mutating the close button's keyEquivalent; a UI test was added to verify that pressing Return confirms the alert and closes the window.

Changes

Cohort / File(s) Summary
Close Window Alert Focus Management
Sources/AppDelegate.swift
Stop mutating the close button's keyEquivalent; assign defaultButtonCell from the close button, set the alert window as initial first responder, and asynchronously make the close button the first responder before presenting the alert.
Close Window Dialog UI Tests
cmuxUITests/CloseWindowConfirmDialogUITests.swift
Added testReturnConfirmsCloseWindowDialog() and two private polling helpers (waitForCloseWindowAlertToDismiss, waitForMainWindowToClose) to verify Return dismisses the alert and closes the main window.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as AppDelegate
    participant AlertWin as Alert Window
    participant MainWin as Main Window

    User->>App: Cmd+Ctrl+W (request close)
    App->>AlertWin: create/show alert
    App->>AlertWin: set defaultButtonCell = closeButton.cell
    App->>AlertWin: set initialFirstResponder = alertWindow
    App->>AlertWin: async -> make closeButton first responder
    Note right of AlertWin: Alert shown with Close button focused
    User->>AlertWin: Press Return
    AlertWin->>App: trigger close action
    App->>MainWin: close main window
    MainWin-->>User: window closed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Poem

🐇 A nibble of focus in moonlight so mellow,
The close button waits, perched steady and yellow,
No tricky Cmd+D to steal the night’s tune,
Return now closes beneath the calm moon. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 summarizes the main fix: enabling Return key to confirm the close dialog after Cmd+Ctrl+W, which is the primary objective of the changeset.
Description check ✅ Passed The description covers the required sections: Summary explains what changed and why, Testing includes verification steps and CI links. Demo video and Review Trigger sections are not provided, but all critical information is present.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch task-close-dialog-enter-focus

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

@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 2 files

@greptile-apps

greptile-apps Bot commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression where pressing Return did not confirm the Cmd+Ctrl+W close-window dialog. The previous implementation accidentally broke Return by overriding the button's keyEquivalent to "d" (Cmd+D), which prevented the defaultButtonCell from responding to Return. The fix removes that override and instead uses initialFirstResponder plus a deferred makeFirstResponder (via DispatchQueue.main.async) to ensure the Close button is focused when the modal opens — a standard macOS pattern. A new XCUITest regression test is added to guard against future regressions.

Key changes:

  • Sources/AppDelegate.swift: Removes Cmd+D key-equivalent override; sets initialFirstResponder and dispatches makeFirstResponder asynchronously so Return correctly triggers the default Close button.
  • cmuxUITests/CloseWindowConfirmDialogUITests.swift: Adds testReturnConfirmsCloseWindowDialog and the supporting waitForCloseWindowAlertToDismiss helper.

Notes:

  • The makeKeyAndOrderFront(nil) call inside the async block is redundant — runModal() already handles this — and could theoretically interact with the modal session if executed after the modal has already returned.
  • The final XCTAssertFalse(app.windows.firstMatch.exists, ...) assertion in the new test has no timeout/retry, which may occasionally fail on slow CI if the window close lags behind the alert dismissal in the accessibility API.

Confidence Score: 4/5

  • This PR is safe to merge; the fix correctly restores Return-key behaviour for the close dialog with only minor style-level refinements suggested.
  • The logic change is small and well-understood (standard NSAlert focus pattern), the bug it fixes is clearly diagnosed, and a new regression test is included. Minor deductions for the redundant makeKeyAndOrderFront and the potentially flaky window-close assertion in the test.
  • cmuxUITests/CloseWindowConfirmDialogUITests.swift — the final XCTAssertFalse on line 58 has no wait/timeout and may be intermittently flaky on slow CI.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Replaces the Cmd+D key-equivalent workaround with proper default-button + first-responder focus setup; the DispatchQueue.main.async pattern is idiomatic for NSAlert modal focus but includes a redundant makeKeyAndOrderFront call.
cmuxUITests/CloseWindowConfirmDialogUITests.swift Adds a regression XCUITest for Return-key confirmation; the dismiss-wait helper is solid, but the final window-existence assertion has no retry/timeout, which can be flaky on slow CI runners.

Sequence Diagram

sequenceDiagram
    participant User
    participant AppDelegate
    participant NSAlert
    participant AlertWindow
    participant MainQueue as DispatchQueue.main

    User->>AppDelegate: Cmd+Ctrl+W
    AppDelegate->>NSAlert: create alert (Close / Cancel)
    AppDelegate->>AlertWindow: defaultButtonCell = closeButton.cell
    AppDelegate->>AlertWindow: initialFirstResponder = closeButton
    AppDelegate->>MainQueue: async { makeKeyAndOrderFront + makeFirstResponder(closeButton) }
    AppDelegate->>NSAlert: runModal() [starts modal loop]
    NSAlert->>AlertWindow: show window, make key
    MainQueue-->>AlertWindow: makeFirstResponder(closeButton) [executes in modal run loop]
    User->>AlertWindow: press Return
    AlertWindow->>NSAlert: defaultButtonCell fires → .alertFirstButtonReturn
    NSAlert-->>AppDelegate: runModal() returns .alertFirstButtonReturn
    AppDelegate->>AppDelegate: confirmCloseMainWindow → true
    AppDelegate->>AlertWindow: performClose(nil)
    AlertWindow-->>User: window closed
Loading

Last reviewed commit: 39e8afb

waitForCloseWindowAlertToDismiss(app: app, timeout: 5.0),
"Expected Return to dismiss the close window confirmation alert"
)
XCTAssertFalse(app.windows.firstMatch.exists, "Expected Return to confirm window close")

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.

No timeout on window-close assertion — potential flakiness

waitForCloseWindowAlertToDismiss only waits until the alert UI element is gone. After that returns, window.performClose(nil) still needs to propagate through the accessibility API before app.windows.firstMatch.exists reads false. On a slow CI machine this assertion can fire before the window has actually closed, producing an intermittent failure.

Consider adding a small wait loop analogous to the other helpers:

// Wait for the app window itself to close after the alert is dismissed
let windowClosedByDeadline = {
    let deadline = Date().addingTimeInterval(5.0)
    while Date() < deadline {
        if !app.windows.firstMatch.exists { return true }
        RunLoop.current.run(until: Date().addingTimeInterval(0.05))
    }
    return !app.windows.firstMatch.exists
}()
XCTAssertFalse(app.windows.firstMatch.exists, "Expected Return to confirm window close")

Or, at minimum, use XCTNSPredicateExpectation / expectation(for:) with a timeout before asserting.

Comment thread Sources/AppDelegate.swift
Comment on lines +4453 to +4456
DispatchQueue.main.async {
alertWindow.makeKeyAndOrderFront(nil)
_ = alertWindow.makeFirstResponder(closeButton)
}

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.

makeKeyAndOrderFront inside async is redundant

NSAlert.runModal() already calls the equivalent of makeKeyAndOrderFront internally when it starts the modal session, so the explicit call here is a no-op in normal conditions. It is harmless, but it introduces a subtle conceptual hazard: if runModal() returns (user clicks a button) extremely quickly — before the async block executes — the block will call makeKeyAndOrderFront on a window that is already being torn down by the modal session, which can cause unexpected visual behaviour.

The focus goal is fully achieved by initialFirstResponder (set synchronously before runModal()) together with the makeFirstResponder async call. Consider dropping the redundant makeKeyAndOrderFront:

Suggested change
DispatchQueue.main.async {
alertWindow.makeKeyAndOrderFront(nil)
_ = alertWindow.makeFirstResponder(closeButton)
}
DispatchQueue.main.async {
_ = alertWindow.makeFirstResponder(closeButton)
}

"Expected Cmd+Ctrl+W to show the close window confirmation alert"
)

app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])

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.

Prefer the typed overload of typeKey

XCUIKeyboardKey.return.rawValue wraps and immediately unwraps the key constant; the API has a direct overload that accepts XCUIKeyboardKey:

Suggested change
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
app.typeKey(.return, modifierFlags: [])

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Addressed the Greptile feedback in 529008cb9: removed the redundant makeKeyAndOrderFront, switched the XCUITest to typeKey(.return, ...), and added a wait for the window itself to disappear after the alert dismisses. Re-running the targeted close-window UI test now.

@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 `@cmuxUITests/CloseWindowConfirmDialogUITests.swift`:
- Around line 78-87: The helper waitForCloseWindowAlertToDismiss currently
returns as soon as the confirmation alert disappears, which can race with the
actual window closing; update waitForCloseWindowAlertToDismiss(app:
XCUIApplication, timeout: TimeInterval) so the polling loop only returns true
when BOTH isCloseWindowAlertPresent(app: app) is false AND
app.windows.firstMatch.exists is false (i.e., wait for the alert to dismiss and
app.windows.firstMatch.exists == false in the same loop iteration), keeping the
same timeout and RunLoop sleep behavior and preserving the final return value
based on those two conditions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d3beb4a-5674-406f-9d1f-70c9353bbad1

📥 Commits

Reviewing files that changed from the base of the PR and between df06579 and 39e8afb.

📒 Files selected for processing (2)
  • Sources/AppDelegate.swift
  • cmuxUITests/CloseWindowConfirmDialogUITests.swift

Comment thread cmuxUITests/CloseWindowConfirmDialogUITests.swift
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Followed up on the remaining CodeRabbit note. I didn't fold window-close waiting into waitForCloseWindowAlertToDismiss because that helper is also used by the Cancel-path test, where the alert should dismiss while the main window stays open. The current shape keeps that helper scoped to alert dismissal and handles the close race separately with waitForMainWindowToClose, which addresses the flake without regressing Cancel coverage.

@lawrencecchen
lawrencecchen merged commit 054b91f into main Mar 12, 2026
14 checks passed
@lawrencecchen
lawrencecchen deleted the task-close-dialog-enter-focus branch March 12, 2026 12:25
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Add close-window return-key regression test

* Focus close-window confirmation button

* Keep Return on close-window alert

* Address review feedback

This branch was successfully deployed

1 active deployment
Preview — 529008cb Deployed Mar 12, 2026 by vercel[bot]
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.

1 participant