Skip to content

Enforce a 400pt main-window minimum height on every resize path - #11321

Merged
azooz2003-bit merged 4 commits into
mainfrom
feat-min-window-height
Sep 1, 2026
Merged

azooz2003-bit merged 4 commits into
mainfrom
feat-min-window-height

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

Resizing the main window very short overflowed the chrome in an ugly way: the sidebar footer (account/help/update pill) collided with workspace rows, the update pill clipped at the window edge, and terminal content spilled over the titlebar during the drag.

Two mechanisms, one floor:

  • SessionPersistencePolicy.minimumWindowHeight goes from 200pt to 400pt. minSize/contentMinSize, the SwiftUI root .frame(minHeight:), restored-frame validation, display-reconfiguration clamps, and the visible-frame fit rescue all already derive from this constant, so user drag resizes now stop at 400pt everywhere.
  • CmuxMainWindow.setFrame now raises undersized programmatic frames to minimumContentSize (top edge anchored, mirroring the existing oversized cap), because AppKit's minSize bounds only user resizes; session-restore math, display reconfiguration, and automation could still deliver a below-floor frame.

The new-window cascade clamp in positionNewMainWindow derives its height from the same policy instead of a stale 360pt literal.

Commit 1 adds the regression test only (red), commit 2 the fix (green): a programmatic setFrame below the floor must clamp, keeping the top edge put.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Enforces a 400pt main-window minimum height on every resize path so the sidebar footer, update pill, and tab bar no longer overlap or clip when the window is short.

Bug Fixes

  • Raises the policy floor from 200pt to 400pt; minSize and contentMinSize already stop user drags, and CmuxMainWindow.setFrame now raises undersized programmatic frames to the floor (session restore, display reconfiguration, automation).
  • The raise pins the edge a live drag keeps still (bottom for top-edge drags, top for bottom-edge drags, right for left-edge drags) so the window doesn't slide; programmatic shrinks keep the titlebar's top-left corner.
  • New-window cascade sizing uses the same policy height instead of a stale 360pt literal.
  • Adds regression tests for the programmatic clamp, edge-pinned raises, and fitting frames passing through unchanged.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved window sizing to prevent non-fullscreen windows from being too small or excessively large.
    • Preserved the window’s top edge when expanding undersized windows.
    • Fullscreen windows retain their selected dimensions.
    • Updated minimum window height requirements to accommodate the full interface layout.
  • Tests

    • Added coverage for automatic window resizing, edge-preserving adjustments, and correctly sized frames.

@vercel

vercel Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
cmux166 Ready Ready Preview Sep 1, 2026 10:59am UTC
cmux41 Ready Ready Preview Sep 1, 2026 10:59am UTC

@coderabbitai

coderabbitai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: 37967695-5eba-4ff9-9261-b19911a98c0d

📥 Commits

Reviewing files that changed from the base of the PR and between 96e9421 and cddb0dc.

📒 Files selected for processing (2)
  • Sources/App/CmuxMainWindow.swift
  • cmuxTests/MainWindowSelfSizingTests.swift

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: 4c48b0b5-e3fb-416c-90fe-64de358d0e70

📥 Commits

Reviewing files that changed from the base of the PR and between d00eb61 and 96e9421.

📒 Files selected for processing (2)
  • Sources/App/CmuxMainWindow.swift
  • cmuxTests/MainWindowSelfSizingTests.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The change enforces minimum dimensions for non-fullscreen windows, aligns startup and session persistence limits with those dimensions, and adds coverage for frame adjustment and edge preservation.

Changes

Window sizing

Layer / File(s) Summary
Window sizing rules
Sources/App/CmuxMainWindow.swift, Sources/AppDelegate.swift, Sources/SessionPersistence.swift
Non-fullscreen frames are capped and raised to minimum content dimensions. Fullscreen frames remain unchanged. Startup uses the shared minimum height, and session persistence requires a 400-point minimum height.
Window sizing validation
cmuxTests/MainWindowSelfSizingTests.swift
Tests verify minimum dimensions, preserved top, bottom, and right edges, and unchanged fitting frames.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 96e94

The change enforces a consistent 400pt minimum window height across interactive, restored, and programmatic resizing paths, preventing layout clipping and overlap. No actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: ejc3, austinywang

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a detailed summary and rationale, but it omits the required Testing section, Demo Video link or attachment, Review Trigger block, and Checklist responses. Add the required Testing section with test commands and verification results. Include a demo video or attachment for the UI behavior change. Add the Review Trigger block and complete the Checklist items.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the primary change: enforcing a 400pt minimum main-window height across resize paths.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 3 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS. The production diff adds no actor-isolation mistake. CmuxMainWindow is an intentional UI type marked @MainActor, and the new pure frameByRaisingUndersizedDimensions helper is explicitly `n…
Cmux Swift Blocking Runtime ✅ Passed The full PR diff adds only synchronous frame-sizing logic, a minimum-height constant, a derived window-size value, and deterministic test assertions. It adds no semaphore or blocking wait, sleep, dela…
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR changes only Sources/App/CmuxMainWindow.swift, Sources/AppDelegate.swift, Sources/SessionPersistence.swift, and cmuxTests/MainWindowSelfSizingTests.swift. The diff contains no `br…
Cmux Expensive Synchronous Load ✅ Passed PASS: The production diff only changes window frame clamping, the new-window minimum height source, and SessionPersistencePolicy.minimumWindowHeight. It adds no agent-history loader, transcript/traj…
Cmux Cache Substitution Correctness ✅ Passed PASS: The PR changes window sizing only. The production diff adds frame clamping, changes the shared minimum-height constant, and derives cascade sizing from that constant. It does not replace a fresh…
Cmux No Hacky Sleeps ✅ Passed PASS: The PR changes only Swift production files and a Swift test file. The merge-base diff contains no TypeScript, JavaScript, shell, or non-Swift build/runtime files. The changed lines introduce no …
Cmux Algorithmic Complexity ✅ Passed PASS. The production diff adds only constant-time frame-size arithmetic, comparisons, and edge adjustments in frameByRaisingUndersizedDimensions, plus shared-constant references. The existing `NSScr…
Cmux Swift Concurrency ✅ Passed PASS: The pull request changes only synchronous AppKit frame handling, a shared sizing constant, and XCTest coverage. The added Swift lines introduce no Dispatch queues/groups, Combine state, completi…
Cmux Swift @Concurrent ✅ Passed PASS: The PR adds no async function or async call site. The only new nonisolated declaration, CmuxMainWindow.frameByRaisingUndersizedDimensions, is synchronous and pure, which the rule allows. `se…
Cmux Swift Package Boundaries ✅ Passed PASS. The production diff keeps the new behavior in CmuxMainWindow.setFrame and its small geometry helper, which are AppKit window glue tied to the main window's drag and fullscreen behavior. The he…
Full details: Cmux Swift Actor Isolation

Explanation

PASS. The production diff adds no actor-isolation mistake. CmuxMainWindow is an intentional UI type marked @MainActor, and the new pure frameByRaisingUndersizedDimensions helper is explicitly nonisolated. The changed AppDelegate call runs inside the existing @MainActor AppDelegate. SessionPersistencePolicy changes only a numeric constant and comments; it adds no model, protocol, Sendable reference, or background store. The added isolation-related test code is @MainActor or pure test code, which the rule allows. Existing nonisolated policy usages were not changed.

Full details: Cmux Swift Blocking Runtime

Explanation

The full PR diff adds only synchronous frame-sizing logic, a minimum-height constant, a derived window-size value, and deterministic test assertions. It adds no semaphore or blocking wait, sleep, delayed dispatch, timer, polling loop, main-queue sync, or manual lock in production Swift. Existing timing and dispatch code in AppDelegate is unchanged by this PR. The new synchronization-related test code is deterministic scaffolding, which the rule allows.

Full details: Cmux Browser Automation Off-Main

Explanation

PASS: The PR changes only Sources/App/CmuxMainWindow.swift, Sources/AppDelegate.swift, Sources/SessionPersistence.swift, and cmuxTests/MainWindowSelfSizingTests.swift. The diff contains no browser.* socket commands, WebKit waits, worker-router changes, processV2Command routing, or control-policy changes. The added @MainActor annotation is on a window sizing test, not browser automation. Therefore the check is not applicable and no off-main browser automation failure is introduced.

Full details: Cmux Expensive Synchronous Load

Explanation

PASS: The production diff only changes window frame clamping, the new-window minimum height source, and SessionPersistencePolicy.minimumWindowHeight. It adds no agent-history loader, transcript/trajectory/workstream read, directory scan, or large JSON parse. Existing RestorableAgentSessionIndex.load() and SharedLiveAgentIndex call sites are unchanged between origin/main and HEAD; no interactive load path is added or worsened.

Full details: Cmux Cache Substitution Correctness

Explanation

PASS: The PR changes window sizing only. The production diff adds frame clamping, changes the shared minimum-height constant, and derives cascade sizing from that constant. It does not replace a fresh authoritative read with a cached value in a persistence, history, undo, or snapshot path. No changed TypeScript or JavaScript files exist, and the added use of currentFrame is transient geometry for drag anchoring, not persisted state. The cache rule's cold-cache and stale-cache conditions are therefore not applicable.

Full details: Cmux No Hacky Sleeps

Explanation

PASS: The PR changes only Swift production files and a Swift test file. The merge-base diff contains no TypeScript, JavaScript, shell, or non-Swift build/runtime files. The changed lines introduce no fixed sleeps, timers, polling, delayed dispatch, or wall-clock waits. The check is therefore inapplicable.

Full details: Cmux Algorithmic Complexity

Explanation

PASS. The production diff adds only constant-time frame-size arithmetic, comparisons, and edge adjustments in frameByRaisingUndersizedDimensions, plus shared-constant references. The existing NSScreen.screens.map and display-frame capping logic remain semantically unchanged; they do not introduce a new collection scan or worsen an existing hot path. The added collection-based code is test-only, which the rule excludes.

Full details: Cmux Swift Concurrency

Explanation

PASS: The pull request changes only synchronous AppKit frame handling, a shared sizing constant, and XCTest coverage. The added Swift lines introduce no Dispatch queues/groups, Combine state, completion-handler APIs, or fire-and-forget Tasks. The @MainActor test annotation is an allowed XCTest/AppKit boundary, and the diff does not materially expand any existing legacy async pattern.

Full details: Cmux Swift `@Concurrent`

Explanation

PASS: The PR adds no async function or async call site. The only new nonisolated declaration, CmuxMainWindow.frameByRaisingUndersizedDimensions, is synchronous and pure, which the rule allows. setFrame and the added test are synchronous @MainActor UI-bound code. The diff adds no @concurrent, does not combine @concurrent with actor isolation, and introduces no CPU-, file-, or network-heavy async helper.

Full details: Cmux Swift Package Boundaries

Explanation

PASS. The production diff keeps the new behavior in CmuxMainWindow.setFrame and its small geometry helper, which are AppKit window glue tied to the main window's drag and fullscreen behavior. The helper has no lifecycle, provider, persistence-schema, singleton, or cross-surface API; its callers are the window override and tests. The AppDelegate cascade change is app-lifecycle composition, and the SessionPersistencePolicy edit only changes an app-specific window-size constant. These cases are allowed by the package-boundary rule.

✨ 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 feat-min-window-height

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.

@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.

2 issues found across 4 files

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="cmuxTests/MainWindowSelfSizingTests.swift">

<violation number="1" location="cmuxTests/MainWindowSelfSizingTests.swift:199">
P3: These new tests in a non-UI cmuxTests file are added as XCTestCase methods, but the repo's Swift Testing policy prefers @Suite/@Test for new non-UI tests even inside existing XCTestCase files, reserving XCTest for UI-interaction suites. Express them as a new @Suite (kept separate from the existing XCTestCase class) instead.</violation>

<violation number="2" location="cmuxTests/MainWindowSelfSizingTests.swift:213">
P3: The regression test undersizes both width (220 < 300) and height (120 < 400), so both dims raise in one call; test 2 covers both-above passthrough. The PR's specific path — height-undersized, width-fitting frame where height is raised to the floor and the width passes through unchanged with the top edge anchored — is left untested. Add a case with width >= minimum (e.g. width 900, height 120) asserting only the y/origin and height change while width stays byte-for-byte.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

let minimum = CmuxMainWindow.minimumContentSize
let undersized = NSRect(x: 80, y: 500, width: 220, height: 120)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The regression test undersizes both width (220 < 300) and height (120 < 400), so both dims raise in one call; test 2 covers both-above passthrough. The PR's specific path — height-undersized, width-fitting frame where height is raised to the floor and the width passes through unchanged with the top edge anchored — is left untested. Add a case with width >= minimum (e.g. width 900, height 120) asserting only the y/origin and height change while width stays byte-for-byte.

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

<comment>The regression test undersizes both width (220 < 300) and height (120 < 400), so both dims raise in one call; test 2 covers both-above passthrough. The PR's specific path — height-undersized, width-fitting frame where height is raised to the floor and the width passes through unchanged with the top edge anchored — is left untested. Add a case with width >= minimum (e.g. width 900, height 120) asserting only the y/origin and height change while width stays byte-for-byte.</comment>

<file context>
@@ -188,4 +188,57 @@ final class MainWindowSelfSizingTests: XCTestCase {
+        }
+
+        let minimum = CmuxMainWindow.minimumContentSize
+        let undersized = NSRect(x: 80, y: 500, width: 220, height: 120)
+        window.setFrame(undersized, display: false)
+
</file context>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The height-only-undersized path with a fitting width is covered by testFrameRaisePinsKeptBottomEdgeDuringTopEdgeDrag and testFrameRaisePinsKeptTopEdgeDuringBottomEdgeDrag (width 1000 ≥ 300, height raised alone), added in e4db0fd275.

/// enforce the floor on every setFrame path, keeping the top edge (the
/// titlebar the user can grab) where the caller put it.
@MainActor
func testSetFrameRaisesUndersizedFrameToMinimumContentSize() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: These new tests in a non-UI cmuxTests file are added as XCTestCase methods, but the repo's Swift Testing policy prefers @Suite/@test for new non-UI tests even inside existing XCTestCase files, reserving XCTest for UI-interaction suites. Express them as a new @suite (kept separate from the existing XCTestCase class) instead.

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

<comment>These new tests in a non-UI cmuxTests file are added as XCTestCase methods, but the repo's Swift Testing policy prefers @Suite/@Test for new non-UI tests even inside existing XCTestCase files, reserving XCTest for UI-interaction suites. Express them as a new @Suite (kept separate from the existing XCTestCase class) instead.</comment>

<file context>
@@ -188,4 +188,57 @@ final class MainWindowSelfSizingTests: XCTestCase {
+    /// enforce the floor on every setFrame path, keeping the top edge (the
+    /// titlebar the user can grab) where the caller put it.
+    @MainActor
+    func testSetFrameRaisesUndersizedFrameToMinimumContentSize() {
+        let window = CmuxMainWindow(
+            contentRect: NSRect(x: 0, y: 0, width: 900, height: 600),
</file context>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Kept as XCTest methods for idiom consistency with the surrounding MainWindowSelfSizingTests class; the pure-function cases sit beside the window-lifecycle behavior they pin.

@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.

All reported issues were addressed across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

Re-trigger cubic

Comment thread Sources/App/CmuxMainWindow.swift Outdated
Comment thread Sources/App/CmuxMainWindow.swift Outdated
azooz2003-bit and others added 3 commits August 31, 2026 22:05
…t clamp

CmuxMainWindow.minSize only bounds user resizes; programmatic setFrame can
still shrink the window below the layout floor, where the sidebar footer,
update pill, and tab bar overlap and clip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Below ~400pt the chrome cannot lay out without overlap: the sidebar footer
(account/help/update pill) collides with workspace rows and the update pill
clips at the window edge. Raise the policy floor from 200pt and make
CmuxMainWindow.setFrame raise undersized programmatic frames to the floor
(top edge anchored), mirroring the existing oversized cap; minSize already
stops user drags there. The new-window cascade clamp now derives its height
from the same policy instead of a stale 360pt literal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Preflight on macOS 26 showed edge drags deliver below-minSize frames
straight through setFrame, so the floor clamp is the only guard during a
live drag. Anchoring the raise to the proposal's top edge made a top-edge
drag slide the whole window down with the cursor once it hit the floor.
Infer the kept edge by comparing the proposal against the current frame
and pin that edge (bottom for top-edge drags, top for bottom-edge drags,
right for left-edge drags); programmatic shrinks that move both edges
keep the titlebar's top-left corner as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A programmatic setFrame that happens to share the window's current
bottom-left origin is not a top-edge drag; only infer the held edge while
inLiveResize, and keep the titlebar's top-left corner for every
programmatic raise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit
azooz2003-bit merged commit 61d0a51 into main Sep 1, 2026
13 of 15 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-min-window-height branch September 1, 2026 07:10
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 1, 2026
717f357 Fix concurrent tmux-compatible set-buffer writes (manaflow-ai#11291)
71d7498 Fix dashboard tab loading flashes (manaflow-ai#11363)
3eb96d8 iOS onboarding: offer Keep Mac Awake once the Mac connects (manaflow-ai#11334)
9f14777 cloud-vm smoke: --paid marks the smoke user as a pro plan (manaflow-ai#11375)
983cccb fix(tests): cmuxTests compiles on main again (two missing lines from manaflow-ai#11059 and manaflow-ai#11345) (manaflow-ai#11346)
9bbf2fe Device-list authorization + DO-first control plane (listauth) (manaflow-ai#11220)
d2a1ac9 vm: gate Cloud VM provisioning behind paid plans (manaflow-ai#11332)
61d0a51 Enforce a 400pt main-window minimum height on every resize path (manaflow-ai#11321)
4c5cb45 Glue terminal frames to live window resize ticks (manaflow-ai#11323)
28b71b2 Cloud tree agent parity: combined follow-ups (manaflow-ai#11300 + manaflow-ai#11301) + one-click workspace rows (manaflow-ai#11345)
c15815b cloud images: minimal shell highlighting, restore devbox bashrc lockstep (manaflow-ai#11343)
bcd3b7b feat: add supported iOS localizations (manaflow-ai#11315)
3523369 test: pin the plan gates for team-less accounts (user-scoped billing must not widen access) (manaflow-ai#11311)
006a4ee blaxel image: bump agent and cua-driver pins to latest (manaflow-ai#11293)
268ca9d web: billing recovery — email-claim redemption, portal for lapsed subscribers, dunning (manaflow-ai#11313)

This branch was successfully deployed

2 active deployments
Preview – cmux41 — cddb0dcf Deployed Sep 1, 2026 by vercel[bot]
Preview – cmux166 — cddb0dcf Deployed Sep 1, 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