Skip to content

Fix hidden browser slot inspector focus crash - #1211

Merged
lawrencecchen merged 2 commits into
mainfrom
issue-1210-hidden-browser-slot-inspector-focus-crash
Mar 12, 2026
Merged

lawrencecchen merged 2 commits into
mainfrom
issue-1210-hidden-browser-slot-inspector-focus-crash

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a regression test that keeps an inspector-owned responder active, hides the browser slot, and asserts the slot yields first responder
  • clear first responder when WindowBrowserSlotView hides or leaves its window so WebKit does not reactivate stale inspector UI on app activation
  • keep the fix scoped to the crash path already explored in Fix browser inspector responder crash on hide #1178

Testing

  • ./scripts/setup.sh
  • ./scripts/reload.sh --tag issue-1210-inspector-focus (build succeeded and launched cmux DEV issue-1210-inspector-focus.app)

Issues


Summary by cubic

Fix a focus crash when a hidden browser slot still owns first responder by yielding it when the slot hides or leaves its window, preventing stale WebKit inspector reactivation on app focus (closes #1210).

  • Bug Fixes
    • WindowBrowserSlotView now yields owned first responder when hidden or when leaving its window.
    • Added a regression test to ensure hidden slots yield any inspector-owned responder.

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

Summary by CodeRabbit

  • Bug Fixes

    • Improved focus management to properly release focus when browser slots are hidden or detached from windows, ensuring smoother window transitions and preventing focus-related issues.
  • Tests

    • Added test coverage for focus release behavior when hiding browser components.

@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 1:13am

@coderabbitai

coderabbitai Bot commented Mar 12, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds responder-management hooks to BrowserWindowPortal that yield owned first responder when browser slots become hidden or are detached from their window. Introduces a private helper to check ownership and resign responders, integrated into slot visibility and window-transition lifecycle events. Includes a corresponding test to verify the fix.

Changes

Cohort / File(s) Summary
Focus Responder Management
Sources/BrowserWindowPortal.swift
Added yieldOwnedFirstResponderIfNeeded(in:reason:) helper to resign first responder if owned by slot or descendants. Extended WindowBrowserSlotView with isHidden observer and viewWillMove(toWindow:) override to yield responders on hide and window detachment.
Responder Behavior Test
cmuxTests/CmuxWebViewKeyEquivalentTests.swift
Added acceptsFirstResponder override to WKInspectorProbeView test fixture and introduced testHidingBrowserSlotYieldsOwnedInspectorFirstResponder test to verify responder yield when slots hide.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 A hidden slot? No keeper keeps,
The responder yields while window sleeps.
No crash shall wake the inspector's dream,
When slots detach from focus stream! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing a crash related to hidden browser slots retaining inspector focus, which directly aligns with the changeset.
Description check ✅ Passed The description covers the Summary section explaining what changed and why, includes Testing section with commands executed, and references closed issues. All key required sections are present and adequately filled.
Linked Issues check ✅ Passed All coding requirements from issue #1210 are met: WindowBrowserSlotView now yields owned first responder when hidden or leaving window, and a regression test verifies this behavior.
Out of Scope Changes check ✅ Passed All changes directly address the crash prevention objective. The test helper override and focus management hooks are scoped to the issue #1210 requirements with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 issue-1210-hidden-browser-slot-inspector-focus-crash

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 crash (issue #1210) where WebKit reactivates a stale inspector UI responder after app activation by ensuring WindowBrowserSlotView resigns any owned first responder before it disappears from the screen.

  • Overrides isHidden with a didSet observer that calls yieldOwnedFirstResponderIfNeeded whenever the slot transitions from visible to hidden while attached to a window.
  • Overrides viewWillMove(toWindow:) to apply the same yield when the slot is removed from its window entirely.
  • Extracts a shared yieldOwnedFirstResponderIfNeeded private helper (reusing the existing browserPortalOwningView + isDescendant(of:) pattern) so both call sites stay DRY.
  • Adds acceptsFirstResponder: true to the existing WKInspectorProbeView test stub and a new regression test (testHidingBrowserSlotYieldsOwnedInspectorFirstResponder) that confirms the slot releases an inspector-owned first responder on hide.
  • Fix is intentionally scoped to the two hiding paths explored in PR Fix browser inspector responder crash on hide #1178; ancestor-visibility changes are not handled (acknowledged trade-off).

Confidence Score: 4/5

  • This PR is safe to merge — the fix is well-scoped, follows existing patterns, and is backed by a targeted regression test.
  • The production code change is minimal and correct: guard conditions, oldValue usage, and super call ordering are all right. The only gap is that the viewWillMove(toWindow:) code path has no corresponding test, leaving a small risk of silent regression for that specific trigger.
  • No files require special attention; the minor gap is a missing test for the viewWillMove path in cmuxTests/CmuxWebViewKeyEquivalentTests.swift.

Important Files Changed

Filename Overview
Sources/BrowserWindowPortal.swift Adds isHidden didSet and viewWillMove(toWindow:) overrides to WindowBrowserSlotView that clear the first responder when the slot hides or leaves its window; factored into a shared yieldOwnedFirstResponderIfNeeded helper — logic is correct, follows existing patterns, and super call order in viewWillMove is correct.
cmuxTests/CmuxWebViewKeyEquivalentTests.swift Adds acceptsFirstResponder: true to WKInspectorProbeView (needed for the precondition) and a new regression test covering the isHidden crash path; the viewWillMove(toWindow:) path introduced in the same PR is not covered by a test.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["isHidden = true (didSet)"] -->|"isHidden && !oldValue && window != nil"| C
    B["viewWillMove(toWindow: nil)"] -->|"newWindow == nil && window != nil"| C
    C["yieldOwnedFirstResponderIfNeeded(in: window)"]
    C --> D{"window.firstResponder\nexists?"}
    D -->|No| Z["return false (no-op)"]
    D -->|Yes| E{"browserPortalOwningView\nnon-nil?"}
    E -->|No| Z
    E -->|Yes| F{"owningView === slot\nor isDescendant(of: slot)?"}
    F -->|No| Z
    F -->|Yes| G["window.makeFirstResponder(nil)"]
    G --> H["First responder cleared\nWebKit stale UI prevented"]
Loading

Last reviewed commit: fce5545

window.firstResponder,
"Hiding a browser slot should yield any owned inspector responder before it goes off-screen"
)
}

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.

Missing test coverage for viewWillMove(toWindow:) path

The PR adds two guard sites — the isHidden didSet and viewWillMove(toWindow: nil) — but the new regression test only exercises the isHidden path. The window-removal path could silently regress without any test catching it. A companion test that removes the slot from its window while an inspector responder is active would give full coverage for this fix.

@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 `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift`:
- Around line 11520-11558: The test
testHidingBrowserSlotYieldsOwnedInspectorFirstResponder never reaches the hide
path because the WKInspectorProbeView used in the test (the nested
BrowserWindowPortalLifecycleTests.WKInspectorProbeView) doesn't override
acceptsFirstResponder, so window.makeFirstResponder(inspectorView) fails; fix by
updating that local WKInspectorProbeView definition to override var
acceptsFirstResponder: Bool { true } so the inspectorView can become first
responder before slot.isHidden is toggled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9b026f5-d9fa-4498-9fef-bef1ced5b412

📥 Commits

Reviewing files that changed from the base of the PR and between 6849b83 and fce5545.

📒 Files selected for processing (2)
  • Sources/BrowserWindowPortal.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift

Comment on lines +11520 to +11558
func testHidingBrowserSlotYieldsOwnedInspectorFirstResponder() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 520, height: 320),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
defer { window.orderOut(nil) }
realizeWindowLayout(window)

guard let contentView = window.contentView else {
XCTFail("Expected content view")
return
}

let slot = WindowBrowserSlotView(frame: NSRect(x: 40, y: 24, width: 260, height: 180))
contentView.addSubview(slot)

let inspectorContainer = NSView(frame: slot.bounds)
inspectorContainer.autoresizingMask = [.width, .height]
let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds)
inspectorView.autoresizingMask = [.width, .height]
inspectorContainer.addSubview(inspectorView)
slot.addSubview(inspectorContainer)
contentView.layoutSubtreeIfNeeded()

XCTAssertTrue(
window.makeFirstResponder(inspectorView),
"Precondition failed: inspector probe should become first responder"
)
XCTAssertTrue(window.firstResponder === inspectorView)

slot.isHidden = true

XCTAssertNil(
window.firstResponder,
"Hiding a browser slot should yield any owned inspector responder before it goes off-screen"
)
}

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

This regression never reaches the hide path.

The acceptsFirstResponder override added on Lines 2437-2438 is on a different nested WKInspectorProbeView type. This test instantiates BrowserWindowPortalLifecycleTests.WKInspectorProbeView on Line 11540, and that local type still inherits NSView.acceptsFirstResponder == false, so the precondition on Line 11547 will fail before slot.isHidden = true is exercised.

Suggested fix
private final class WKInspectorProbeView: NSView {
    override var acceptsFirstResponder: Bool { true }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift` around lines 11520 - 11558,
The test testHidingBrowserSlotYieldsOwnedInspectorFirstResponder never reaches
the hide path because the WKInspectorProbeView used in the test (the nested
BrowserWindowPortalLifecycleTests.WKInspectorProbeView) doesn't override
acceptsFirstResponder, so window.makeFirstResponder(inspectorView) fails; fix by
updating that local WKInspectorProbeView definition to override var
acceptsFirstResponder: Bool { true } so the inspectorView can become first
responder before slot.isHidden is toggled.

@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: fce5545675

ℹ️ 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 on lines +11546 to +11548
XCTAssertTrue(
window.makeFirstResponder(inspectorView),
"Precondition failed: inspector probe should become first responder"

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 Use first-responder-capable probe in slot-hide test

This precondition assumes window.makeFirstResponder(inspectorView) can succeed, but the WKInspectorProbeView used in BrowserWindowPortalLifecycleTests still inherits NSView without overriding acceptsFirstResponder, which defaults to false in AppKit. In that case this assertion fails and the regression does not actually cover the slot-hiding focus-yield path the fix relies on.

Useful? React with 👍 / 👎.

@lawrencecchen
lawrencecchen merged commit 817066b into main Mar 12, 2026
20 checks passed
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…n-browser-slot-inspector-focus-crash

Fix hidden browser slot inspector focus crash

This branch was successfully deployed

1 active deployment
Preview — fce55456 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.

cmux NIGHTLY crashes when a hidden browser slot still owns WebKit inspector focus

1 participant