Walk each pane host's focus items once per run loop turn

Merged
TE
5d ago
+18-0

Summary

A deep split chain (an agent team's column of teammates) froze cmux's main thread for tens of seconds when it mounted and on switching back to it (manaflow-ai/cmux#10311, manaflow-ai/cmux#16393).

A main-thread sample of the hang shows this sequence. Each time a pane mounts, SwiftUI's AppKitPlatformViewHost.viewDidMoveToSuperview rebuilds the key view loop of its subtree (-[NSView _setDefaultKeyViewLoop]). For every hosting view on that path, SwiftUI answers acceptsFirstResponder by walking its focusable items (FocusBridge.hostAcceptsFirstResponder). That walk asks each nested pane host the same question, so the same nested walks repeat at every level of the chain. AppKit's autorecalculatesKeyViewLoop window flag doesn't gate this path.

NonDraggableHostingView (the pane and split-child host) now keeps its acceptsFirstResponder answer until the end of the current run-loop turn, so each host is walked once per turn. The answer is computed fresh on the next turn, so focus changes inside a pane are still seen.

This branch is based on the commit cmux currently pins (2e21c47), not on main, so cmux can point at it without also taking #282, which manaflow-ai/cmux#19593 bumps separately. Please merge with a merge commit so this commit stays reachable from main.

Testing

cmux's tests_v2/test_deep_split_chain_stays_responsive.py (manaflow-ai/cmux#19606) opens a 16-deep chain, shows it, switches away and back, and checks that the socket keeps answering.

  • Red on cmux main: RESULT 2/5. Switching away timed out at 10 s, and switching back took 8.9 s.
  • With this commit (cmux build of manaflow-ai/cmux#19606 head d089d71f): RESULT 5/5. Opening, showing, switching away and switching back each answered in 0.08 s or less.

🤖 Generated with Claude Code