Skip to content

Follow up equalize splits shortcut fixes - #3309

Merged
austinywang merged 8 commits into
mainfrom
codex/equalize-splits-shortcut-followup
May 4, 2026
Merged

austinywang merged 8 commits into
mainfrom
codex/equalize-splits-shortcut-followup

Conversation

@austinywang

@austinywang austinywang commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • follows up [codex] Expose equalize splits as a keyboard shortcut #3200 with review and manual-verification fixes for the equalize-splits shortcut
  • keeps Cmd+Ctrl+= configurable through KeyboardShortcutSettings/settings.json and adds a dedicated shortcut label localization key
  • makes equalize splits a silent no-op for single-pane workspaces across keyboard/menu/palette paths
  • reasserts equalized divider positions on deferred main-queue follow-up passes so nested horizontal and vertical splits settle from one shortcut invocation
  • refreshes the workspace layout snapshot and terminal geometry reconcile path after programmatic equalize
  • hardens the shortcut routing regression test so it proves the seeded layout is uneven before equalizing and verifies the cached layout refreshes

Verification

  • git diff --check
  • JSON parse: Resources/Localizable.xcstrings, web/data/cmux-settings.schema.json
  • ./scripts/reload.sh --tag codex-equalize-splits-shortcut-followup --launch
  • Manual tagged build verification: created uneven nested horizontal + vertical splits, sent actual Cmd+Ctrl+=, and verified one invocation changed root 925/405 to 665/665 and nested 298/618 to 458/458 without another click or keypress
  • Not run: local unit tests, because the repo runner invokes xcodebuild directly

References #3200.


Note

Medium Risk
Touches split-geometry manipulation and shortcut routing, including async follow-up passes and layout snapshot refreshes that could affect pane layout/focus timing. No auth or data-handling changes.

Overview
Adds a dedicated Equalize Splits shortcut action (default Cmd+Ctrl+=) with new localization (shortcut.equalizeSplits.label), and wires it through keyboard handling, the command palette, and a new main-menu command.

Refactors equalize logic into TabManager+EqualizeSplits to run multi-pass divider normalization (including deferred main-queue follow-ups) and explicitly refresh workspace layout state after programmatic geometry changes, while making single-pane workspaces a silent no-op (debug-logged only).

Hardens shortcut/settings parsing by allowing settings-file shortcut normalization without system-wide conflict checks, and adds regression tests covering shortcut routing, nested-split equalization, and settings-file parsing.

Reviewed by Cursor Bugbot for commit 0aaf57a. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added "Equalize Splits" command and menu item with a default Cmd+Ctrl+= shortcut to balance split panes to 50%.
  • Behavior

    • Command runs only when a workspace is selected and performs multi-pass rebalancing with layout reconciliation to ensure stable results.
  • Localization

    • Added comprehensive localized labels for the new command across many languages.
  • Tests

    • Added UI and settings tests validating the shortcut and equalization behavior.

Ghostty already has an equalize_splits action, but cmux did not expose an editable app-level shortcut for restoring split pane divider balance. Route Cmd+Ctrl+= through the cmux shortcut layer to the existing TabManager equalize implementation, show that shortcut in the command palette/menu/docs, and allow settings.json overrides via the existing shortcut registry.

Constraint: New cmux-owned shortcuts must be in KeyboardShortcutSettings, settings.json schema, and docs.

Rejected: Rely on the existing RPC workaround | not discoverable for keyboard-driven pane workflows

Confidence: medium

Scope-risk: narrow

Directive: Keep equalize_splits wired to the existing TabManager.equalizeSplits path unless split layout ownership changes.

Tested: git diff --check; JSON parse for web/data/cmux-settings.schema.json and Resources/Localizable.xcstrings

Not-tested: Full Xcode app build blocked locally because Zig is not installed and GhosttyKit.xcframework is absent
@vercel

vercel Bot commented Apr 29, 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 May 4, 2026 2:20am
cmux-staging Building Building Preview, Comment May 4, 2026 2:20am

@coderabbitai

coderabbitai Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an "Equalize Splits" command and keyboard shortcut (Cmd+Ctrl+=) that centers all split dividers (0.5) in the selected workspace; implements core equalization, UI/menu wiring, AppDelegate routing, follow-up reattempts and geometry reconcile, localization, tests, and project file updates.

Changes

Equalize Splits Feature

Layer / File(s) Summary
Action Definition & Defaults
Sources/KeyboardShortcutSettings.swift
Adds KeyboardShortcutSettings.Action.equalizeSplits, localized label shortcut.equalizeSplits.label, and default StoredShortcut(key: "=", command: true, shift: false, option: false, control: true) (Cmd+Ctrl+=).
Shortcut Routing / Performer
Sources/AppDelegate.swift, Sources/AppDelegate+EqualizeSplitsShortcut.swift
handleCustomShortcut now matches .equalizeSplits and calls performEqualizeSplitsShortcut() which invokes tabManager?.equalizeSplits(tabId:) for the selected workspace.
Command Palette & ContentView
Sources/ContentView+RightSidebarCommandPalette.swift, Sources/ContentView.swift
Maps "palette.equalizeSplits" to .equalizeSplits; palette command calls tabManager.equalizeSplits(tabId:) only when a workspace is selected and no longer emits a beep on failure.
Menu Integration
Sources/cmuxApp+EqualizeSplitsMenu.swift, Sources/cmuxApp.swift
Adds equalizeSplitsCommandButton() with localized title and .equalizeSplits shortcut, inserts it into windowAndViewCommands, and widens activeTabManager visibility for menu usage.
Core Equalization Logic
Sources/TabManager+EqualizeSplits.swift, Sources/TabManager.swift
Implements TabManager.equalizeSplits(tabId:) plus equalizeSplitsOnce(in:) and recursive traversal to set split divider positions to 0.5; on success it calls tab.didProgrammaticallyChangeSplitGeometry() and schedules immediate and delayed (0.15s) follow-up reattempts that conditionally re-trigger the geometry-change signal.
Workspace Geometry Handling
Sources/Workspace+EqualizeSplitsSupport.swift, Sources/Workspace.swift
Adds Workspace.didProgrammaticallyChangeSplitGeometry() to capture bonsplitController.layoutSnapshot() into tmuxLayoutSnapshot and call scheduleTerminalGeometryReconcile(); makes tmuxLayoutSnapshot writable and widens scheduleTerminalGeometryReconcile() visibility.
Localization
Resources/Localizable.xcstrings
Adds shortcut.equalizeSplits.label with extractionState = "manual" and localizations.{en,ja,zh-Hans,zh-Hant,ko,de,es,fr,it,da,pl,ru,bs,ar,nb,pt-BR,th,tr,uk}.stringUnit.value translations.
Web Shortcut Data
web/data/cmux-shortcuts.ts
Adds equalizeSplits entry to split-panes shortcut category (id, key combo, bilingual description).
Tests
cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift, cmuxTests/KeyboardShortcutSettingsEqualizeSplitsTests.swift
Adds integration test that synthesizes Cmd+Ctrl+= and verifies dividers become 0.5 and layout snapshot/frame consistency; adds settings-file parsing test for equalizeSplits override.
Project File
GhosttyTabs.xcodeproj/project.pbxproj
Registers new source and test files for the feature and tests in file references, groups, and build phases.
Scripts
scripts/reload.sh
Minor unrelated change: APP_NAME uses sanitized TAG_SLUG in tag mode.

Sequence Diagram

sequenceDiagram
    actor User
    participant OS
    participant AppDelegate
    participant TabManager
    participant Workspace
    participant BonsplitController

    User->>OS: Press Cmd+Ctrl+=
    OS->>AppDelegate: deliver shortcut event
    AppDelegate->>AppDelegate: matchConfiguredShortcut(.equalizeSplits)
    AppDelegate->>AppDelegate: performEqualizeSplitsShortcut()
    AppDelegate->>TabManager: equalizeSplits(tabId:)
    TabManager->>TabManager: equalizeSplitsOnce(in:)
    TabManager->>BonsplitController: traverse tree & set divider = 0.5
    BonsplitController-->>TabManager: report success/failure
    TabManager->>Workspace: didProgrammaticallyChangeSplitGeometry()
    Workspace->>BonsplitController: layoutSnapshot()
    BonsplitController-->>Workspace: return layout snapshot
    Workspace->>Workspace: scheduleTerminalGeometryReconcile()
    Note over TabManager: Schedule immediate follow-up reattempt
    Note over TabManager: Schedule delayed reattempt (0.15s)
    TabManager->>BonsplitController: re-traverse & verify equalization persists
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
I hop through panes where dividers sit,
Cmd+Ctrl+=—I nudge them to split.
Centered beams, a tidy midst,
Joyful thump: "Equalize the splits!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: a follow-up PR that fixes and improves the equalize splits shortcut feature from a previous PR (#3200).
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.
Description check ✅ Passed The pull request description follows the template structure with Summary, Testing, Verification, and additional context provided comprehensively.

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

✨ 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 codex/equalize-splits-shortcut-followup

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR follows up #3200 by wiring equalizeSplits into all three invocation paths (keyboard shortcut, menu, command palette), converting it to a silent no-op for single-pane workspaces, and fixing the post-equalize geometry refresh so tmuxLayoutSnapshot and terminal sizes stay in sync. The regression test is meaningfully hardened by seeding uneven dividers and asserting both the live and cached layouts converge after firing the shortcut.

Confidence Score: 4/5

Safe to merge; only P2 findings present — an edge-case stale-snapshot scenario and a test helper gap.

All findings are P2. The partial-equalization snapshot issue requires setDividerPosition to fail on at least one split while succeeding on others — an unlikely runtime condition given IDs are sourced from the live snapshot. The test helper oversight is a quality concern, not a correctness blocker.

Sources/TabManager.swift — the didEqualize guard condition that gates didProgrammaticallyChangeSplitGeometry().

Important Files Changed

Filename Overview
Sources/TabManager.swift Adds didProgrammaticallyChangeSplitGeometry() call after equalize, but only when ALL splits succeeded — partial success leaves the tmux snapshot stale.
Sources/Workspace.swift Adds didProgrammaticallyChangeSplitGeometry() — correctly snaps the layout and schedules geometry reconcile.
Sources/AppDelegate.swift Adds keyboard shortcut handler for equalizeSplits action; silently no-ops when no workspace is selected.
Sources/ContentView.swift Removes NSSound.beep() for palette equalize-splits command and adds palette-to-action mapping — aligns with silent no-op policy.
Sources/KeyboardShortcutSettings.swift Adds .equalizeSplits case with default Cmd+Ctrl+= binding, localization label, and convenience accessor — clean addition following existing patterns.
Sources/cmuxApp.swift Adds Equalize Splits menu item using existing splitCommandButton helper and command.equalizeSplits.title localization key (which already exists in xcstrings).
cmuxTests/AppDelegateShortcutRoutingTests.swift Adds integration test that seeds uneven splits, fires Cmd+Ctrl+=, and verifies both divider positions and cached snapshot refresh. Helper shortcutRoutingAssertPaneFramesMatch silently skips extra panes on key-set mismatch.
cmuxTests/WorkspaceUnitTests.swift Extends KeyboardShortcutSettings file-store test to cover equalizeSplits override parsing — straightforward addition.
Resources/Localizable.xcstrings Adds shortcut.equalizeSplits.label with translations in 20 locales; command.equalizeSplits.title already existed pre-PR.
web/data/cmux-settings.schema.json Adds equalizeSplits to the allowed shortcut action enum in the JSON schema.
web/data/cmux-shortcuts.ts Adds equalizeSplits entry with ⌃⌘= combo and English/Japanese descriptions to the shortcuts reference data.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Keyboard / Menu / Palette]) --> B{selectedWorkspace?}
    B -- no --> C[silent no-op]
    B -- yes --> D[TabManager.equalizeSplits]
    D --> E[traverse treeSnapshot recursively]
    E --> F{setDividerPosition 0.5 for each split}
    F -- all succeed --> G[didEqualize = true]
    F -- any fails --> H[allSucceeded = false\ndidEqualize = false]
    G --> I[tab.didProgrammaticallyChangeSplitGeometry]
    H --> J[snapshot NOT refreshed\n⚠️ stale if any splits moved]
    I --> K[tmuxLayoutSnapshot = layoutSnapshot]
    I --> L[scheduleTerminalGeometryReconcile]
Loading

Reviews (1): Last reviewed commit: "Make equalize splits shortcut merge-read..." | Re-trigger Greptile

Comment thread Sources/TabManager.swift Outdated
Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift Outdated

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmuxTests/WorkspaceUnitTests.swift (1)

679-701: ⚠️ Potential issue | 🟡 Minor

Use the actual = binding here.

The fixture currently records cmd+ctrl+e, so this test does not exercise the new Cmd+Ctrl+= shortcut. If the equalize-splits binding regresses, this assertion would still pass.

🛠️ Suggested fix
-                "equalizeSplits": "cmd+ctrl+e",
+                "equalizeSplits": "cmd+ctrl+=",
-            StoredShortcut(key: "e", command: true, shift: false, option: false, control: true)
+            StoredShortcut(key: "=", command: true, shift: false, option: false, control: true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/WorkspaceUnitTests.swift` around lines 679 - 701, The test fixture
currently uses "cmd+ctrl+e" so it doesn't validate the new equalize-splits
binding; update the JSON fixture string where the "equalizeSplits" binding is
specified to use "cmd+ctrl+=", and then update the expected assertion for
store.override(for: .equalizeSplits) to expect StoredShortcut(key: "=", command:
true, shift: false, option: false, control: true) so
KeyboardShortcutSettingsFileStore.override(for:) is actually validated for the
"=" key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Around line 7767-7770: The registry callback currently calls
tabManager.equalizeSplits(tabId:) and ignores its Bool result; change the
callback (used in ContentView.swift alongside similar usages in cmuxApp.swift
and AppDelegate.swift) to capture the return value and handle failure (e.g., log
an error via your logger, present an alert to the user, or update UI state) when
equalizeSplits returns false so failures such as "no splits found" or "divider
change rejected" are surfaced rather than silently discarded.

---

Outside diff comments:
In `@cmuxTests/WorkspaceUnitTests.swift`:
- Around line 679-701: The test fixture currently uses "cmd+ctrl+e" so it
doesn't validate the new equalize-splits binding; update the JSON fixture string
where the "equalizeSplits" binding is specified to use "cmd+ctrl+=", and then
update the expected assertion for store.override(for: .equalizeSplits) to expect
StoredShortcut(key: "=", command: true, shift: false, option: false, control:
true) so KeyboardShortcutSettingsFileStore.override(for:) is actually validated
for the "=" key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 970eb3ef-e4b3-4fe7-bddb-aec7ee8d07e7

📥 Commits

Reviewing files that changed from the base of the PR and between e181f99 and c337696.

📒 Files selected for processing (11)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/WorkspaceUnitTests.swift
  • web/data/cmux-settings.schema.json
  • web/data/cmux-shortcuts.ts

Comment thread Sources/ContentView.swift
Follow-up to community PR #3200. The shortcut was exposed, but review and manual verification found three rough edges: the regression did not prove the seeded layout was uneven, no-op single-pane invocations beeped, and a single programmatic equalize pass let parent split resizing leave nested split views stale until a later interaction.

This keeps the shortcut configurable through KeyboardShortcutSettings and settings.json, gives its Settings label a dedicated localization key, treats single-pane equalize as a silent no-op, and reasserts equalized divider positions on deferred main-queue follow-up passes so nested horizontal and vertical splits settle from one shortcut invocation.

Constraint: Follow-up to community PR #3200; push only to manaflow-ai/cmux

Rejected: Treat equalize false as user-facing error | single-pane workspaces are a valid no-op

Rejected: Only refresh terminal geometry cache | manual verification showed the nested NSSplitView itself still needed a deferred divider reassertion

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Tested: JSON parse for Resources/Localizable.xcstrings and web/data/cmux-settings.schema.json

Tested: ./scripts/reload.sh --tag codex-equalize-splits-shortcut-followup --launch

Tested: Manual tagged build verification with uneven nested horizontal+vertical splits; one Cmd+Ctrl+= changed root 925/405 to 665/665 and nested 298/618 to 458/458 without another click or keypress

Not-tested: Local unit tests because repo runner invokes xcodebuild directly

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

🧹 Nitpick comments (1)
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

772-787: Derive the trigger from the configured shortcut instead of hard-coding Cmd+Ctrl+=.

equalizeSplits is now settings-backed, so this regression is more stable if it synthesizes the event from the resolved shortcut for .equalizeSplits rather than baking in the current default. Otherwise a future settings.json default change will break the test without a routing regression.

Based on learnings: KeyboardShortcutSettings.setShortcut(...) is a no-op when KeyboardShortcutSettings.isManagedBySettingsFile(action) returns true, preserving the settings file as the source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 772 - 787, Test
currently hard-codes a Cmd+Ctrl+=" key event; instead fetch the resolved
shortcut for the .equalizeSplits action (via the KeyboardShortcutSettings API
used in the app) and synthesize the keydown event from that shortcut (use the
resolved key, modifiers and keyCode when calling makeKeyDownEvent) so the test
follows settings-backed shortcuts; ensure you still call
appDelegate.debugHandleCustomShortcut(event: event) and keep the DEBUG-only
assertion, and do not call KeyboardShortcutSettings.setShortcut(...) in the test
when KeyboardShortcutSettings.isManagedBySettingsFile(action) may be true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 772-787: Test currently hard-codes a Cmd+Ctrl+=" key event;
instead fetch the resolved shortcut for the .equalizeSplits action (via the
KeyboardShortcutSettings API used in the app) and synthesize the keydown event
from that shortcut (use the resolved key, modifiers and keyCode when calling
makeKeyDownEvent) so the test follows settings-backed shortcuts; ensure you
still call appDelegate.debugHandleCustomShortcut(event: event) and keep the
DEBUG-only assertion, and do not call KeyboardShortcutSettings.setShortcut(...)
in the test when KeyboardShortcutSettings.isManagedBySettingsFile(action) may be
true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3cc4997-d817-4fb1-960e-834fa175c02e

📥 Commits

Reviewing files that changed from the base of the PR and between c337696 and c8a583e.

📒 Files selected for processing (9)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/WorkspaceUnitTests.swift
✅ Files skipped from review due to trivial changes (4)
  • Resources/Localizable.xcstrings
  • cmuxTests/WorkspaceUnitTests.swift
  • Sources/Workspace.swift
  • Sources/ContentView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/cmuxApp.swift

…litGeometry to Workspace+EqualizeSplitsSupport.swift

Preserve main's refactored command, menu, schema, and shortcut structure while reapplying the equalize-splits feature through the shared shortcut action and TabManager geometry path. Keep file-length CI green by moving the new geometry-cache helper out of Workspace and the shortcut parser assertion out of WorkspaceUnitTests.

Constraint: Build must use ./scripts/reload.sh with the branch tag; local test suites are intentionally not run.\nRejected: Bump Swift file-length budgets | CI requires staying within existing budgets.\nConfidence: high\nScope-risk: moderate\nTested: git diff --check; git diff --cached --check; conflict-marker scan; swift_file_length_budget.py; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; remote verified as origin=https://github.com/manaflow-ai/cmux.git\nNot-tested: Local unit/UI test suites, per repo policy.
…apshots

The helper now lives outside Workspace.swift to satisfy the file-length budget, so the cached tmux layout snapshot needs a module-internal setter for same-module Workspace extensions.

Constraint: Workspace.swift must remain within the existing file-length budget.\nRejected: Put didProgrammaticallyChangeSplitGeometry back in Workspace.swift | would regress the budget fix.\nConfidence: high\nScope-risk: narrow\nTested: git diff --check; swift_file_length_budget.py; conflict-marker scan.\nNot-tested: Local unit/UI test suites, per repo policy.

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

🧹 Nitpick comments (3)
Sources/AppDelegate+EqualizeSplitsShortcut.swift (2)

3-4: 💤 Low value

Redundant double optional-chain on tabManager — capture it once.

Inside the if let workspace block, tabManager is already proven non-nil (it returned a non-nil selectedWorkspace), yet line 4 re-applies ?.. Capturing tabManager upfront removes the redundant check and aligns the style with the command-palette caller (Context snippet 2), which holds a captured non-optional reference.

♻️ Proposed refactor
 extension AppDelegate {
     func performEqualizeSplitsShortcut() {
-        if let workspace = tabManager?.selectedWorkspace {
-            _ = tabManager?.equalizeSplits(tabId: workspace.id)
+        guard let tabManager, let workspace = tabManager.selectedWorkspace else { return }
+        `#if` DEBUG
+        cmuxDebugLog("equalizeSplits shortcut; workspaceId=\(workspace.id)")
+        `#endif`
+        _ = tabManager.equalizeSplits(tabId: workspace.id)
-        }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate`+EqualizeSplitsShortcut.swift around lines 3 - 4, The
code redundantly optional-chains tabManager inside the if let workspace block;
instead capture tabManager once and use the non-optional reference when calling
equalizeSplits. Change the conditional to bind tabManager and its
selectedWorkspace together (e.g. if let tabManager = tabManager, let workspace =
tabManager.selectedWorkspace) and then call tabManager.equalizeSplits(tabId:
workspace.id) (remove the `?.`), referencing the symbols tabManager,
selectedWorkspace, equalizeSplits, tabId, and workspace.id.

2-6: ⚡ Quick win

Add cmuxDebugLog tracing per the AppDelegate debug-logging guideline.

performEqualizeSplitsShortcut() is a keyboard-shortcut handler living in an AppDelegate extension, which squarely falls under the "log key events in AppDelegate.swift" rule. Without a debug trace it is invisible in the ring-buffer log when diagnosing shortcut routing regressions.

♻️ Proposed addition
 extension AppDelegate {
     func performEqualizeSplitsShortcut() {
+        `#if` DEBUG
+        cmuxDebugLog("equalizeSplits shortcut invoked; hasWorkspace=\(tabManager?.selectedWorkspace != nil)")
+        `#endif`
         if let workspace = tabManager?.selectedWorkspace {
             _ = tabManager?.equalizeSplits(tabId: workspace.id)
         }
     }
 }

As per coding guidelines: "Log key events in AppDelegate.swift … use cmuxDebugLog("message") … all call sites must be wrapped in #if DEBUG / #endif."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate`+EqualizeSplitsShortcut.swift around lines 2 - 6, Add a
DEBUG-only cmuxDebugLog call to the performEqualizeSplitsShortcut() handler to
record the shortcut invocation and relevant context; specifically, inside
performEqualizeSplitsShortcut() (before calling tabManager?.equalizeSplits) wrap
a cmuxDebugLog invocation in `#if` DEBUG / `#endif` and log a concise message
including the workspace id (e.g., workspace.id) and that equalizeSplits was
requested so the shortcut routing shows up in AppDelegate traces.
cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift (1)

76-76: ⚡ Quick win

Consider widening the RunLoop wait to reduce timing flakiness.

The scheduleEqualizeSplitsFollowUp defers a second equalize pass via asyncAfter(deadline: .now() + 0.15). The current 0.2 s wait leaves only ~50 ms of margin; under CI scheduler pressure this can cause the assertion to run before the follow-up pass settles.

♻️ Suggested change
-        RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.2))
+        RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.35))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift` at line 76, The
RunLoop wait in the test is too tight for scheduleEqualizeSplitsFollowUp (which
calls asyncAfter(deadline: .now() + 0.15)), so increase the wait duration (e.g.,
from 0.2s to something larger like 0.35–0.5s) where RunLoop.main.run(until:
Date(timeIntervalSinceNow: 0.2)) is used in
AppDelegateEqualizeSplitsShortcutTests to give the follow-up equalize pass
enough margin under CI scheduling.
🤖 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/AppDelegateEqualizeSplitsShortcutTests.swift`:
- Around line 71-75: The else branch currently calls
XCTFail("debugHandleCustomShortcut is only available in DEBUG") but does not
stop execution, causing the test to continue into RunLoop.main.run(...) and
subsequent assertions; after the XCTFail in the non-DEBUG branch add an explicit
return so the test exits early (i.e., modify the `#else` to call XCTFail(...)
followed by return) to prevent spurious cascading failures from
appDelegate.debugHandleCustomShortcut not being invoked.

In `@Sources/KeyboardShortcutSettings.swift`:
- Line 158: The equalize-splits shortcut handler performEqualizeSplitsShortcut()
in AppDelegate+EqualizeSplitsShortcut.swift is missing the transient
terminal-focus guard; add an early return that calls
shouldSuppressSplitShortcutForTransientTerminalFocusState() and exits if true,
mirroring the checks in splitRight and splitDown. Keep the rest of the method
unchanged (it should still call bonsplitController to adjust geometry and then
didProgrammaticallyChangeSplitGeometry()), so the operation is suppressed
whenever the terminal view is hidden, not attached, or lacks a focused panel.

In `@Sources/TabManager`+EqualizeSplits.swift:
- Around line 56-65: The code currently does an early return when
UUID(uuidString: splitNode.id) fails inside the .split case, which stops
recursion into splitNode.first/second; change the guard to handle the nil UUID
without returning — mark allSucceeded = false when UUID parsing fails and then
continue to recurse into splitNode.first and splitNode.second (same recursion
used when the UUID is valid). Keep the existing
controller.setDividerPosition(0.5, forSplit: splitId) failure handling (set
allSucceeded = false) only when you have a valid splitId; ensure the .split case
never returns early so nested valid splits are still equalized by
equalizeSplitsOnce.
- Line 63: In TabManager+EqualizeSplits.swift update the setDividerPosition call
to pass fromExternal: true so it matches other layout-change paths; specifically
change the invocation on controller.setDividerPosition(..., forSplit: splitId)
to include fromExternal: true (keeping the same 0.5 position and splitId) so
downstream geometry notification/reconciliation behaves consistently with
resizeSplit, TerminalController, and workspace restore.

---

Nitpick comments:
In `@cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift`:
- Line 76: The RunLoop wait in the test is too tight for
scheduleEqualizeSplitsFollowUp (which calls asyncAfter(deadline: .now() +
0.15)), so increase the wait duration (e.g., from 0.2s to something larger like
0.35–0.5s) where RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.2)) is
used in AppDelegateEqualizeSplitsShortcutTests to give the follow-up equalize
pass enough margin under CI scheduling.

In `@Sources/AppDelegate`+EqualizeSplitsShortcut.swift:
- Around line 3-4: The code redundantly optional-chains tabManager inside the if
let workspace block; instead capture tabManager once and use the non-optional
reference when calling equalizeSplits. Change the conditional to bind tabManager
and its selectedWorkspace together (e.g. if let tabManager = tabManager, let
workspace = tabManager.selectedWorkspace) and then call
tabManager.equalizeSplits(tabId: workspace.id) (remove the `?.`), referencing
the symbols tabManager, selectedWorkspace, equalizeSplits, tabId, and
workspace.id.
- Around line 2-6: Add a DEBUG-only cmuxDebugLog call to the
performEqualizeSplitsShortcut() handler to record the shortcut invocation and
relevant context; specifically, inside performEqualizeSplitsShortcut() (before
calling tabManager?.equalizeSplits) wrap a cmuxDebugLog invocation in `#if` DEBUG
/ `#endif` and log a concise message including the workspace id (e.g.,
workspace.id) and that equalizeSplits was requested so the shortcut routing
shows up in AppDelegate traces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3859313f-32e5-4d8b-accb-1d85b02ffdea

📥 Commits

Reviewing files that changed from the base of the PR and between c8a583e and f5fa041.

📒 Files selected for processing (15)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate+EqualizeSplitsShortcut.swift
  • Sources/AppDelegate.swift
  • Sources/ContentView+RightSidebarCommandPalette.swift
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/TabManager+EqualizeSplits.swift
  • Sources/TabManager.swift
  • Sources/Workspace+EqualizeSplitsSupport.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp+EqualizeSplitsMenu.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift
  • cmuxTests/KeyboardShortcutSettingsEqualizeSplitsTests.swift
💤 Files with no reviewable changes (1)
  • Sources/TabManager.swift
✅ Files skipped from review due to trivial changes (2)
  • cmuxTests/KeyboardShortcutSettingsEqualizeSplitsTests.swift
  • Resources/Localizable.xcstrings

Comment thread cmuxTests/AppDelegateEqualizeSplitsShortcutTests.swift
Comment thread Sources/KeyboardShortcutSettings.swift
Comment thread Sources/TabManager+EqualizeSplits.swift
Comment thread Sources/TabManager+EqualizeSplits.swift Outdated
Branch-style tags can contain slashes. reload.sh already derives a filesystem-safe tag slug for paths, sockets, and DerivedData; use that same slug for the default copied app bundle name so the mandatory branch-name tag can build and launch.

Constraint: The user-required reload command passes a branch name containing '/'.\nRejected: Require a different --tag value | task requires the branch name exactly.\nConfidence: high\nScope-risk: narrow\nTested: bash -n scripts/reload.sh; git diff --check scripts/reload.sh.\nNot-tested: Local test suites, per repo policy.
Comment thread Sources/Workspace.swift Outdated

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

🧹 Nitpick comments (1)
scripts/reload.sh (1)

340-340: Consider keeping a separate DISPLAY_NAME for plist entries to preserve original-case tags in the Dock

Using TAG_SLUG instead of TAG correctly fixes filesystem-safety and pkill-pattern issues (lines 543, 662). However, CFBundleName and CFBundleDisplayName (lines 548–551) will now display as lowercase-hyphenated slugs (e.g., cmux DEV my-feature) instead of the original mixed-case tag. If developers rely on human-readable tag names in the macOS Dock or Activity Monitor, consider using TAG for a separate DISPLAY_NAME variable in the plist while retaining APP_NAME as the sanitized slug for paths and process patterns.

Note: scripts/reloads.sh (staging) still sets APP_NAME="cmux STAGING ${TAG}" (line 112) and shares the same filesystem-safety risk, but that is outside this PR's scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/reload.sh` at line 340, The plist currently uses the sanitized
APP_NAME ("APP_NAME=\"cmux DEV ${TAG_SLUG}\"") which makes
CFBundleName/CFBundleDisplayName display the slug; introduce a separate
DISPLAY_NAME variable built from TAG (preserving original case) and use
DISPLAY_NAME for CFBundleName and CFBundleDisplayName while keeping APP_NAME as
the filesystem/process-safe slug (TAG_SLUG) for paths, pkill patterns and other
uses; update the plist generation to reference DISPLAY_NAME for human-facing
fields and leave APP_NAME unchanged for all filesystem/process operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/reload.sh`:
- Line 340: The plist currently uses the sanitized APP_NAME ("APP_NAME=\"cmux
DEV ${TAG_SLUG}\"") which makes CFBundleName/CFBundleDisplayName display the
slug; introduce a separate DISPLAY_NAME variable built from TAG (preserving
original case) and use DISPLAY_NAME for CFBundleName and CFBundleDisplayName
while keeping APP_NAME as the filesystem/process-safe slug (TAG_SLUG) for paths,
pkill patterns and other uses; update the plist generation to reference
DISPLAY_NAME for human-facing fields and leave APP_NAME unchanged for all
filesystem/process operations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9064e31-d4f3-4498-904e-00d6a128fd10

📥 Commits

Reviewing files that changed from the base of the PR and between f5fa041 and 2498d07.

📒 Files selected for processing (2)
  • Sources/Workspace.swift
  • scripts/reload.sh

Settings-file parsing should normalize action-specific shortcut shapes without consulting the shared settings store that is currently being constructed. Using the existing conflict-ignoring normalization path prevents the launch-time dispatch_once recursion crash while keeping numbered and system-wide shortcut validation.

Constraint: KeyboardShortcutSettingsFileStore.swift is at its file-length budget.\nRejected: Use full normalizedRecordedShortcut during config load | it consults KeyboardShortcutSettings.settingsFileStore and recursively initializes the store.\nConfidence: high\nScope-risk: narrow\nTested: git diff --check; swift_file_length_budget.py.\nNot-tested: Local unit/UI test suites, per repo policy.

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 82221b4. Configure here.

Comment thread Sources/TabManager+EqualizeSplits.swift Outdated

@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 `@Sources/KeyboardShortcutSettingsFileStore.swift`:
- Around line 865-868: resolvedRecordedShortcutIgnoringConflicts still triggers
KeyboardShortcutSettings.shared during config parsing because
normalizedSystemWideHotkeyShortcutResult calls systemWideHotkeyConflicts which
in turn queries reservedSystemWideHotkeyShortcuts and the live store; to fix,
change normalizedSystemWideHotkeyShortcutResult (or
resolvedRecordedShortcutIgnoringConflicts) to accept a "parsing" flag (or detect
CmuxSettingsFileStore.parse context) and when parsing is true skip calling
systemWideHotkeyConflicts (i.e., treat conflicts as non-existent) OR modify
reservedSystemWideHotkeyShortcuts to return a static reserved list without
querying KeyboardShortcutSettings.shortcut(for:) or
settingsFileStore.override(for:), ensuring no access to the shared store during
parse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf024f2c-874d-4750-96a2-88c8dfb55563

📥 Commits

Reviewing files that changed from the base of the PR and between 2498d07 and 82221b4.

📒 Files selected for processing (1)
  • Sources/KeyboardShortcutSettingsFileStore.swift

Comment thread Sources/KeyboardShortcutSettingsFileStore.swift Outdated
Review feedback showed the equalize shortcut could mutate some divider positions while skipping snapshot refresh, terminal reconciliation, or follow-up passes when another divider failed. The handler now uses the same transient terminal-focus guard as split creation, records DEBUG routing traces, and debug-logs failed no-op paths without surfacing single-pane workspaces to users. Equalize traversal now keeps recursing after malformed split IDs, uses Bonsplit's external-change path, and refreshes workspace geometry whenever a split node was visited. Shortcut config parsing also avoids live system-wide conflict checks while loading settings files so it cannot recurse through the shared store during initialization.

Constraint: Single-pane equalize remains a silent no-op for users.

Rejected: Surface an alert on false equalize results | The PR behavior explicitly keeps single-pane workspaces silent; DEBUG logging gives diagnostics without changing UX.

Confidence: high

Scope-risk: moderate

Directive: Keep programmatic split geometry changes on the same Workspace splitTabBar(didChangeGeometry:) path so layout snapshot, terminal geometry, and focus reconciliation stay coupled.

Tested: git diff --check; ./scripts/reload.sh --tag pr3309-comments

Not-tested: Local unit tests; project policy says tests run in CI/prefer CI.
The conflict-bypass fix for settings-file parsing was behaviorally correct, but formatting alone pushed two already-budgeted Swift files over the CI line-count guard. Collapse the added parsing arguments back into the existing compact style without changing the recursion fix.

Constraint: workflow-guard-tests enforces existing Swift file length budgets.

Rejected: Refresh the budget | This PR can stay inside the current budget without accepting new debt.

Confidence: high

Scope-risk: narrow

Tested: git diff --check; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; ./scripts/reload.sh --tag pr3309-comments

Not-tested: Local unit tests; project policy says tests run in CI/prefer CI.
@austinywang
austinywang merged commit db3e200 into main May 4, 2026
20 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 0aaf57a1 Deployed May 4, 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