Fix native fullscreen unreachable on the main window (#5933)
Fixes #5933.
Problem
On macOS 26 (Tahoe), cmux's main window cannot enter native fullscreen by any route:
- Command Palette Toggle Full Screen → no-op
- ⌃⌘F → no-op
- Green traffic-light button → only zooms/maximizes; never moves to its own fullscreen Space
All three entrypoints resolve the right CmuxMainWindow and call toggleFullScreen(_:), so window targeting is fine. The symptom set (toggle is a no-op and the green button zooms instead of fullscreening) is the textbook signature of a window that is not fullscreen-capable.
Root cause
CmuxMainWindow is created programmatically (never from a nib), so it cannot inherit fullscreen capability from Interface Builder. It never declared .fullScreenPrimary and relied on AppKit implicitly granting fullscreen to a resizable, titled window.
That implicit grant is not reliable across macOS versions. Verified empirically on macOS 26.5 (Tahoe): a window created with cmux's exact style mask ([.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView]) reports collectionBehavior.rawValue == 0 — i.e. it carries none of .fullScreenPrimary / .fullScreenAuxiliary / .fullScreenNone, and AppKit does not treat it as fullscreen-capable. This matches CLAUDE.md's standing warning that AppKit semantics change silently between macOS majors, so implicit defaults must not be assumed stable.
Fix
Declare .fullScreenPrimary explicitly in CmuxMainWindow's initializer, via a pure, unit-testable canonicalCollectionBehavior(_:) helper. The helper:
- inserts
.fullScreenPrimaryso native fullscreen is always reachable, - strips any stale
.fullScreenNone(mutually exclusive with primary), - preserves unrelated bits, so it composes with the temporary
.fullScreenDisallowsTilingopt-out the window factory layers on when spawning a window out of an existing fullscreen Space.
It is idempotent and a no-op where AppKit would have granted fullscreen anyway, so it cannot regress setups that already worked.
Tests
Two-commit red/green:
- Commit 1 adds
CmuxMainWindowFullScreenCapabilityTests(wired intoproject.pbxproj). The window-instantiation test fails on current code because the default collection behavior lacks.fullScreenPrimary. - Commit 2 adds the fix, turning the test green, plus pure-function tests for the
canonicalCollectionBehavior(_:)contract (adds primary, drops stale none, preserves unrelated bits, idempotent).
Scope note: sleep/wake size shrink
The issue also mentions the window shrinking after display sleep/wake. The reporter is on v0.64.14; the sleep/wake reposition fix (#6305, CmuxMainWindow.constrainFrameRect) landed in v0.64.17 / nightly and is already on main, which lines up with the reporter's note that the bug "does not reproduce on NIGHTLY." This PR targets the fullscreen-capability defect, which is the part still present on main and is not addressed anywhere else. The fullscreen change is orthogonal to and composes with the existing constrain-frame logic.
Localization
No user-facing strings added or changed — the "Toggle Full Screen" command and its command.toggleFullScreen.* strings already exist in Localizable.xcstrings. Change is window-capability code + tests + pbxproj wiring only.
🤖 Generated with Claude Code
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.
Summary by cubic
Make the main window explicitly fullscreen-capable so native fullscreen works again via Toggle Full Screen, ⌃⌘F, and the green button on macOS 26 and multi-monitor setups. Fixes #5933.
- Bug Fixes
- Add
canonicalCollectionBehavior(_:)and apply it inCmuxMainWindowinit to insert.fullScreenPrimary, remove.fullScreenNone, and preserve other behavior bits. - Add tests for fullscreen capability and the helper’s contract (adds primary, drops none, preserves bits, idempotent), now using Swift Testing (
@Suite,@Test).
- Add