Repository navigation
cmux-next: agent chat is a docked column with no tab bar by default - #18223
Conversation
Red: set-column-dock and move-tab-to-column drop role, identify does not advertise dock-column-role-v1, the registry record drops it, and the app never sends it back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cmux-tui: ColumnDock gains an optional role (agent_chat), set with the pin by set-column-dock and move-tab-to-column, journaled and restored with it, dropped with it, and read as no role when unknown. identify advertises dock-column-role-v1. Spec and generated bindings updated. cmux next: DockSnapshot, the dock requests and the layout's DockColumn carry the role; no behavior uses it yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent chat dock (a left dock column with the agent_chat role) never gets tabs of its own. A person's New Terminal, New Browser or Cmd-T from it opens a tab in the scrolling strip, in the strip pane focused last; Cmd-T's agent or page kind becomes a New Tab page there. A chat alone on its screen moves into a new left chat dock and the new terminal or browser stays in its pane, in one daemon commit. Chats outside the chat dock stay normal panes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its cwd NEW-TERMINAL-INHERITS-CWD: a terminal opened from the docked agent chat (a strip tab, or the tab a lone chat leaves when it docks) sends only an explicit cwd and otherwise nil, so the daemon's resolver (new-terminal-cwd-v1) decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… column Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScreenLayout.column(containing:) only searches real columns, so a new workspace's screen (one split tree) had none and New Terminal from its lone chat opened beside the chat. LayoutScreen.column(containing:) gives the implicit column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tabs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tabs The agent chat is a docked column with no tab bar by default. The chat dock's pane (the dock column with the agent_chat role) and a chat alone on its screen, which docks when the first tool opens, hide their strip while they hold one tab. The strip comes back at two tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every new agent chat (New Agent Chat, Cmd-I on a pane, the CLI, Duplicate, a session link) opens in the chat dock: a tab in the existing dock, else a new left dock with the agent_chat role made from the chat once it exists. A chat alone on its screen keeps a second chat in its pane; it docks when the first tool opens. A New Tab page that becomes a chat stays where it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
…e strip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e strip A new chat bound for a new chat dock opens unselected and hidden from its pane's strip (pendingDock), then shows once, in its dock, focused. A failed move shows it where it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat-role # Conflicts: # cmux-tui/bindings/cpp/.cmux-sdk-manifest.json # cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp # cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/go/raw/generated_commands.go # cmux-tui/bindings/go/raw/generated_events.go # cmux-tui/bindings/go/raw/generated_metadata.go # cmux-tui/bindings/go/raw/generated_presence_test.go # cmux-tui/bindings/go/raw/generated_types.go # cmux-tui/bindings/java/src/com/cmux/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/java/src/com/cmux/raw/Protocol.java # cmux-tui/bindings/python/cmux/raw/_generated/.cmux-sdk-manifest.json # cmux-tui/bindings/python/cmux/raw/_generated/_schema.py # cmux-tui/bindings/python/cmux/raw/_generated/metadata.py # cmux-tui/bindings/rust/src/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/rust/src/generated/commands.rs # cmux-tui/bindings/rust/src/generated/events.rs # cmux-tui/bindings/rust/src/generated/metadata.rs # cmux-tui/bindings/rust/src/generated/mod.rs # cmux-tui/bindings/rust/src/generated/types.rs # cmux-tui/bindings/typescript/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/typescript/src/raw/generated/commands.ts # cmux-tui/bindings/typescript/src/raw/generated/events.ts # cmux-tui/bindings/typescript/src/raw/generated/index.ts # cmux-tui/bindings/typescript/src/raw/generated/metadata.ts # cmux-tui/bindings/typescript/src/raw/generated/types.ts # cmux-tui/bindings/zig/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/zig/src/raw/generated/protocol.zig # cmux-tui/spec/commands.md
…ock-tools # Conflicts: # cmux-tui/bindings/cpp/.cmux-sdk-manifest.json # cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp # cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/go/raw/generated_commands.go # cmux-tui/bindings/go/raw/generated_events.go # cmux-tui/bindings/go/raw/generated_metadata.go # cmux-tui/bindings/go/raw/generated_presence_test.go # cmux-tui/bindings/go/raw/generated_types.go # cmux-tui/bindings/java/src/com/cmux/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/java/src/com/cmux/raw/Protocol.java # cmux-tui/bindings/python/cmux/raw/_generated/.cmux-sdk-manifest.json # cmux-tui/bindings/python/cmux/raw/_generated/_schema.py # cmux-tui/bindings/python/cmux/raw/_generated/metadata.py # cmux-tui/bindings/rust/src/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/rust/src/generated/commands.rs # cmux-tui/bindings/rust/src/generated/events.rs # cmux-tui/bindings/rust/src/generated/metadata.rs # cmux-tui/bindings/rust/src/generated/mod.rs # cmux-tui/bindings/rust/src/generated/types.rs # cmux-tui/bindings/typescript/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/typescript/src/raw/generated/commands.ts # cmux-tui/bindings/typescript/src/raw/generated/events.ts # cmux-tui/bindings/typescript/src/raw/generated/index.ts # cmux-tui/bindings/typescript/src/raw/generated/metadata.ts # cmux-tui/bindings/typescript/src/raw/generated/types.ts # cmux-tui/bindings/zig/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/zig/src/raw/generated/protocol.zig # cmux-tui/spec/commands.md
…ock-default # Conflicts: # cmux-tui/bindings/cpp/.cmux-sdk-manifest.json # cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp # cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/go/raw/generated_commands.go # cmux-tui/bindings/go/raw/generated_events.go # cmux-tui/bindings/go/raw/generated_metadata.go # cmux-tui/bindings/go/raw/generated_presence_test.go # cmux-tui/bindings/go/raw/generated_types.go # cmux-tui/bindings/java/src/com/cmux/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/java/src/com/cmux/raw/Protocol.java # cmux-tui/bindings/python/cmux/raw/_generated/.cmux-sdk-manifest.json # cmux-tui/bindings/python/cmux/raw/_generated/_schema.py # cmux-tui/bindings/python/cmux/raw/_generated/metadata.py # cmux-tui/bindings/rust/src/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/rust/src/generated/commands.rs # cmux-tui/bindings/rust/src/generated/events.rs # cmux-tui/bindings/rust/src/generated/metadata.rs # cmux-tui/bindings/rust/src/generated/mod.rs # cmux-tui/bindings/rust/src/generated/types.rs # cmux-tui/bindings/typescript/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/typescript/src/raw/generated/commands.ts # cmux-tui/bindings/typescript/src/raw/generated/events.ts # cmux-tui/bindings/typescript/src/raw/generated/index.ts # cmux-tui/bindings/typescript/src/raw/generated/metadata.ts # cmux-tui/bindings/typescript/src/raw/generated/types.ts # cmux-tui/bindings/zig/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/zig/src/raw/generated/protocol.zig # cmux-tui/spec/commands.md
|
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
…hat-role # Conflicts: # cmux-tui/bindings/cpp/.cmux-sdk-manifest.json # cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp # cmux-tui/bindings/cpp/src/raw/generated/protocol.cpp # cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/go/raw/generated_commands.go # cmux-tui/bindings/go/raw/generated_events.go # cmux-tui/bindings/go/raw/generated_metadata.go # cmux-tui/bindings/go/raw/generated_presence_test.go # cmux-tui/bindings/go/raw/generated_types.go # cmux-tui/bindings/java/src/com/cmux/raw/.cmux-sdk-manifest.json # cmux-tui/bindings/java/src/com/cmux/raw/Protocol.java # cmux-tui/bindings/java/src/com/cmux/raw/SetColumnDockRequest.java # cmux-tui/bindings/python/cmux/raw/_generated/.cmux-sdk-manifest.json # cmux-tui/bindings/python/cmux/raw/_generated/_schema.py # cmux-tui/bindings/python/cmux/raw/_generated/client.py # cmux-tui/bindings/python/cmux/raw/_generated/metadata.py # cmux-tui/bindings/python/cmux/raw/_generated/models.py # cmux-tui/bindings/rust/src/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/rust/src/generated/commands.rs # cmux-tui/bindings/rust/src/generated/events.rs # cmux-tui/bindings/rust/src/generated/metadata.rs # cmux-tui/bindings/rust/src/generated/mod.rs # cmux-tui/bindings/rust/src/generated/types.rs # cmux-tui/bindings/typescript/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/typescript/src/raw/generated/commands.ts # cmux-tui/bindings/typescript/src/raw/generated/events.ts # cmux-tui/bindings/typescript/src/raw/generated/index.ts # cmux-tui/bindings/typescript/src/raw/generated/metadata.ts # cmux-tui/bindings/typescript/src/raw/generated/types.ts # cmux-tui/bindings/zig/src/raw/generated/.cmux-sdk-manifest.json # cmux-tui/bindings/zig/src/raw/generated/protocol.zig # cmux-tui/crates/cmux-tui-core/src/model/layout_columns.rs # cmux-tui/crates/cmux-tui-core/src/mux/dock_columns.rs # cmux-tui/crates/cmux-tui-core/src/mux/tests/column_update.rs # cmux-tui/crates/cmux-tui-core/src/mux/tests/dock_columns.rs # cmux-tui/crates/cmux-tui-core/src/server.rs # cmux-tui/crates/cmux-tui-core/src/server/capabilities.rs # cmux-tui/crates/cmux-tui-core/src/server/dock_columns_tests.rs # cmux-tui/crates/cmux-tui-core/src/workspace_registry/resource_store/screen_rows.rs # cmux-tui/crates/cmux-tui-core/src/workspace_registry/resource_store/viewport.rs # cmux-tui/spec/sdk-schema.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
openAgentTab's dock routing moves to NewChatPlacement.opening, so PaneController stays under the 1000-line god-type limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lines Base grew PaneController; merged with the tip it spanned 1004 lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ock-tools # Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/TabLifecycle.swift
| imageView.autoresizingMask = [.minXMargin, .maxXMargin, bar.isFlipped ? .maxYMargin : .minYMargin] | ||
| imageView.frame = NSRect(x: (b.width - 40) / 2 + shift, y: y, width: 40, height: 40) | ||
| imageView.autoresizingMask = shift == 0 ? [.minXMargin, .maxXMargin, bar.isFlipped ? .maxYMargin : .minYMargin] | ||
| : [.maxXMargin, bar.isFlipped ? .maxYMargin : .minYMargin] |
There was a problem hiding this comment.
Avatar drifts on window resize
Low Severity
With a sidebar, AvatarHolder places the disc at window center plus shift but then uses only .maxXMargin. A later window resize leaves the disc at the old x while the transcript center moves, so the avatar no longer lines up with the name pill.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a60c68f. Configure here.
There was a problem hiding this comment.
Not part of this PR: the MessagesLab vendor files came in through an earlier base merge and are gone from the diff after the latest one.
…ock-tools # Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/TabLifecycle.swift
This comment has been minimized.
This comment has been minimized.
The conflicts were the generated cmux-tui SDK bindings: the dock column role landed on base separately. Took base's side and regenerated them with cmux-tui/bindings/codegen/generate.py (--check is clean). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conflicts were the generated cmux-tui SDK bindings: the dock column role landed on base separately. Took base's side and regenerated them with cmux-tui/bindings/codegen/generate.py (--check is clean). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w tab here Cursor review on #18170: .newColumn is built for a chat dock with no scrolling column, which the daemon never keeps (an all-docked screen undocks), and a zoomed screen maps to its zoomed pane alone, so a zoomed chat looked like a lone chat and docked. resolve takes a zoomed seam, still ignored here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile zoomed, and opens the chat's context Cursor review on #18170: - The daemon never keeps a dock without a scrolling column (an all-docked screen undocks), so .newColumn is gone; it also dropped an explicit cwd and sent browsers and New Tab pages into the dock. - A zoomed screen maps to its zoomed pane alone; nothing docks or moves until the zoom ends. - A URL-less browser from the docked chat opens what the chat works on (#16620), in the strip pane. - A browser a lone chat leaves when it docks is recorded for Auto tabs.newTabKind, like any other browser (Cursor on #18223). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…other docks; the strip shows beside the chat dock Cursor review on #18223: - pendingDock holds the provisional id, and the created id only after the creation reply, so the strip can list the created chat in between. - A finished dock clears every pending chat of the pane. - hidesStrip takes a chat alone in the strip for the screen's lone chat even when a chat dock exists. dockFinished is the finish seam, still clearing everything here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ther docks; the strip shows beside the chat dock Cursor review on #18223: - The strip hides pendingDock through the store's aliases, so the tab that replaces a provisional chat is hidden from its first snapshot. - dockFinished removes only that chat; other chats on their way stay hidden. - The new chat's focus waits until the layout shows it outside the pane it moved from. - A chat alone in the strip beside a chat dock keeps its strip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Takes #18170's Cursor fixes (no strip-less .newColumn, zoom keeps the tab here, the chat's context for a URL-less browser). The one conflict was ChatColumnPlacement.resolve: kept the zoom check and this branch's isChat helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…18170) * test: the dock column keeps an agent chat role Red: set-column-dock and move-tab-to-column drop role, identify does not advertise dock-column-role-v1, the registry record drops it, and the app never sends it back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: dock columns keep an agent chat role (dock-column-role-v1) cmux-tui: ColumnDock gains an optional role (agent_chat), set with the pin by set-column-dock and move-tab-to-column, journaled and restored with it, dropped with it, and read as no role when unknown. identify advertises dock-column-role-v1. Spec and generated bindings updated. cmux next: DockSnapshot, the dock requests and the layout's DockColumn carry the role; no behavior uses it yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-sdk): build ColumnPin with its optional role Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): tools opened from the docked chat go to the strip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cmux-next): tools opened from the docked chat open in the strip The agent chat dock (a left dock column with the agent_chat role) never gets tabs of its own. A person's New Terminal, New Browser or Cmd-T from it opens a tab in the scrolling strip, in the strip pane focused last; Cmd-T's agent or page kind becomes a New Tab page there. A chat alone on its screen moves into a new left chat dock and the new terminal or browser stays in its pane, in one daemon commit. Chats outside the chat dock stay normal panes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a lone New Tab page is not a chat to dock Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a terminal from the docked chat lets the daemon pick its cwd NEW-TERMINAL-INHERITS-CWD: a terminal opened from the docked agent chat (a strip tab, or the tab a lone chat leaves when it docks) sends only an explicit cwd and otherwise nil, so the daemon's resolver (new-terminal-cwd-v1) decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a lone chat on a split-tree screen sees its implicit column Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a lone chat on a split-tree screen docks ScreenLayout.column(containing:) only searches real columns, so a new workspace's screen (one split tree) had none and New Terminal from its lone chat opened beside the chat. LayoutScreen.column(containing:) gives the implicit column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(cmux-next): DockRole in its own file (god-file limit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(cmux-tui): rustfmt the merged permanent-dock test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a strip-less chat dock and a zoomed chat keep the new tab here Cursor review on #18170: .newColumn is built for a chat dock with no scrolling column, which the daemon never keeps (an all-docked screen undocks), and a zoomed screen maps to its zoomed pane alone, so a zoomed chat looked like a lone chat and docked. resolve takes a zoomed seam, still ignored here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): chat dock routing drops the strip-less case, holds while zoomed, and opens the chat's context Cursor review on #18170: - The daemon never keeps a dock without a scrolling column (an all-docked screen undocks), so .newColumn is gone; it also dropped an explicit cwd and sent browsers and New Tab pages into the dock. - A zoomed screen maps to its zoomed pane alone; nothing docks or moves until the zoom ends. - A URL-less browser from the docked chat opens what the chat works on (#16620), in the strip pane. - A browser a lone chat leaves when it docks is recorded for Auto tabs.newTabKind, like any other browser (Cursor on #18223). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: retrigger after a runner disk refusal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
There are 4 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 92ee982. Configure here.
| var dockBound: Set<String> { | ||
| let aliases = services.agentTabs.aliases | ||
| return pendingDock.union(pendingDock.compactMap { aliases[$0] }) | ||
| } |
There was a problem hiding this comment.
Alias cleanup reveals dock-bound tab
High Severity
dockBound hides a rekeyed chat only through aliases, but releaseGoneTabs drops that mapping as soon as the provisional id leaves the tree. That is the same snapshot that first lists the created tab, and pendingDock does not gain the real id until pending.value() returns. A store echo before the create reply therefore shows the chat in the source strip, then hides it again, which is the flash this path is meant to prevent.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 92ee982. Configure here.
| guard columns.contains(where: { $0.root.contains(pane) }) else { return .here } | ||
| if let dock = columns.first(where: { $0.dock?.role == .agentChat }) { return .dock(dock.root.panes[0]) } | ||
| return isLoneChat ? .here : .newDock | ||
| } |
There was a problem hiding this comment.
Concurrent chats create duplicate docks
Medium Severity
resolve treats any screen without an existing agentChat dock as .newDock. Nothing records an in-flight dock, so two chats opened before the first move-tab-to-column lands both create a left chat dock. The daemon then undocks the earlier column on that edge, leaving one chat in a scrolling column instead of both tabs in the chat dock.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 92ee982. Configure here.
| let pane = controller.layoutPaneID | ||
| return resolve(from: pane, columns: ChatColumnPlacement.columns(of: screen, containing: pane), | ||
| isLoneChat: ChatColumnPlacement.resolve(from: controller, services: controller.services) == .dockChat) | ||
| } |
There was a problem hiding this comment.
Zoomed panes still create chat docks
Medium Severity
NewChatPlacement uses ChatColumnPlacement.resolve == .dockChat as isLoneChat and never reads the zoomed flag. A zoomed screen returns .here from that helper, so a lone zoomed chat (or zoomed tools with no dock) is treated as .newDock and still builds a left chat dock. Tool routing already refuses to dock or move while zoomed.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 92ee982. Configure here.
* test: the dock column keeps an agent chat role Red: set-column-dock and move-tab-to-column drop role, identify does not advertise dock-column-role-v1, the registry record drops it, and the app never sends it back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: dock columns keep an agent chat role (dock-column-role-v1) cmux-tui: ColumnDock gains an optional role (agent_chat), set with the pin by set-column-dock and move-tab-to-column, journaled and restored with it, dropped with it, and read as no role when unknown. identify advertises dock-column-role-v1. Spec and generated bindings updated. cmux next: DockSnapshot, the dock requests and the layout's DockColumn carry the role; no behavior uses it yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-sdk): build ColumnPin with its optional role Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): tools opened from the docked chat go to the strip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cmux-next): tools opened from the docked chat open in the strip The agent chat dock (a left dock column with the agent_chat role) never gets tabs of its own. A person's New Terminal, New Browser or Cmd-T from it opens a tab in the scrolling strip, in the strip pane focused last; Cmd-T's agent or page kind becomes a New Tab page there. A chat alone on its screen moves into a new left chat dock and the new terminal or browser stays in its pane, in one daemon commit. Chats outside the chat dock stay normal panes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a lone New Tab page is not a chat to dock Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a terminal from the docked chat lets the daemon pick its cwd NEW-TERMINAL-INHERITS-CWD: a terminal opened from the docked agent chat (a strip tab, or the tab a lone chat leaves when it docks) sends only an explicit cwd and otherwise nil, so the daemon's resolver (new-terminal-cwd-v1) decides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a lone chat on a split-tree screen sees its implicit column Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a lone chat on a split-tree screen docks ScreenLayout.column(containing:) only searches real columns, so a new workspace's screen (one split tree) had none and New Terminal from its lone chat opened beside the chat. LayoutScreen.column(containing:) gives the implicit column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): the chat dock shows no tab strip until it holds two tabs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cmux-next): the chat dock shows no tab strip until it holds two tabs The agent chat is a docked column with no tab bar by default. The chat dock's pane (the dock column with the agent_chat role) and a chat alone on its screen, which docks when the first tool opens, hide their strip while they hold one tab. The strip comes back at two tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a new agent chat opens in the chat dock Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(cmux-next): a new agent chat opens in the chat dock Every new agent chat (New Agent Chat, Cmd-I on a pane, the CLI, Duplicate, a session link) opens in the chat dock: a tab in the existing dock, else a new left dock with the agent_chat role made from the chat once it exists. A chat alone on its screen keeps a second chat in its pane; it docks when the first tool opens. A New Tab page that becomes a chat stays where it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a new chat bound for the chat dock never shows in the strip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a new chat moves into its dock without flashing in the strip A new chat bound for a new chat dock opens unselected and hidden from its pane's strip (pendingDock), then shows once, in its dock, focused. A failed move shows it where it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): the chat dock cannot be split and takes only chats Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): the chat dock refuses splits and non-chat tabs The agent chat dock is a docked region, not a split pane. splitRoom, which every split, auto layout and split drop goes through, refuses splitting it, and moving a terminal, browser or tab group into it is refused with a localized reason. Chats still move in, and tabs already in the dock still reorder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(cmux-next): DockRole in its own file (god-file limit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(cmux-tui): rustfmt the merged permanent-dock test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(cmux-next): new chat routing lives in NewChatPlacement openAgentTab's dock routing moves to NewChatPlacement.opening, so PaneController stays under the 1000-line god-type limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(cmux-next): splitRoom's doc names the chat dock rule (AppServices line budget) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): ChatDockRules imports CmuxNextDaemon for PaneModel and TabModel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(cmux-next): fold PaneController's chat dock notes into fewer lines Base grew PaneController; merged with the tip it spanned 1004 lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: run cmux-next again (generated files never got a runner on 01c19d2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a strip-less chat dock and a zoomed chat keep the new tab here Cursor review on #18170: .newColumn is built for a chat dock with no scrolling column, which the daemon never keeps (an all-docked screen undocks), and a zoomed screen maps to its zoomed pane alone, so a zoomed chat looked like a lone chat and docked. resolve takes a zoomed seam, still ignored here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): chat dock routing drops the strip-less case, holds while zoomed, and opens the chat's context Cursor review on #18170: - The daemon never keeps a dock without a scrolling column (an all-docked screen undocks), so .newColumn is gone; it also dropped an explicit cwd and sent browsers and New Tab pages into the dock. - A zoomed screen maps to its zoomed pane alone; nothing docks or moves until the zoom ends. - A URL-less browser from the docked chat opens what the chat works on (#16620), in the strip pane. - A browser a lone chat leaves when it docks is recorded for Auto tabs.newTabKind, like any other browser (Cursor on #18223). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): a dock-bound chat stays hidden across its rekey and other docks; the strip shows beside the chat dock Cursor review on #18223: - pendingDock holds the provisional id, and the created id only after the creation reply, so the strip can list the created chat in between. - A finished dock clears every pending chat of the pane. - hidesStrip takes a chat alone in the strip for the screen's lone chat even when a chat dock exists. dockFinished is the finish seam, still clearing everything here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): a dock-bound chat stays hidden across its rekey and other docks; the strip shows beside the chat dock Cursor review on #18223: - The strip hides pendingDock through the store's aliases, so the tab that replaces a provisional chat is hidden from its first snapshot. - dockFinished removes only that chat; other chats on their way stay hidden. - The new chat's focus waits until the layout shows it outside the pane it moved from. - A chat alone in the strip beside a chat dock keeps its strip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): an edge drop does not split the chat dock; a workspace drop skips it Cursor review on #18290: a drop commits the split its preview measured (roomDecided) without splitRoom, so the chat dock split; a drop on a workspace in the sidebar targets the screen's first pane, the chat dock when it is on the left, which refuses it. dropSplit and workspaceDropPane are the seams, still with the old behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): edge drops and group actions do not split the chat dock; workspace drops skip it Cursor review on #18290: - A drop committing the split its preview measured (roomDecided) still asks ChatDockRules: the chat dock refuses it, for tabs and groups. - A drop on a workspace in the sidebar lands in its first pane outside the chat dock, so it is not refused. - tabGroup.moveToNewSplit refuses the chat dock, and tabGroup.moveToWorkspace skips it, like the drag paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: retrigger after an unrelated HomeCloudLinkTests timeout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: retrigger after an unrelated DiffSidecarProcessTests flake Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: rerun swift tests after an unrelated timeout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
AgentTabStore spanned 1007 lines (limit 1000) after #18744's chat menu wiring and #18223, so every PR's god-type check failed. The chat tab's header actions and empty-space menu move to AgentChatTabActions, and the store keeps one-line hookups. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>


Summary
Lawrence (blocking): "agent chat should not show tabbar by default (agent chat should be a docked column by default)". This builds on #18170 (routing) and #18169 (the
agent_chatdock role).agent_chatrole.Not in this PR:
dockfield onnew-conversation-tabthat does it in one commit comes after PR4 and PR5, so other clients also see one change.layout.dockAgentChatand Dock/Undock Chat in the next PR. That PR needs the settings page's locale bundles rebuilt.PaneContentView), and the agent pane's own header is unchanged.Testing
Runs use
scripts/measure/suite-runner.shon a fleet runner.theChatDockShowsNoStripWithOneTabandaLoneChatShowsNoStripfailed; the two tests that keep the strip passedaNewChatJoinsTheChatDockandaNewChatBesideToolsMakesTheChatDockfailedChatDockChromeTests,NewChatPlacementTests,ChatColumnPlacementTests,NewTabKindTests,StripNewTabTests,SurfaceTabBarTests,AgentHandlerTestsaTabBoundForTheChatDockNeverShowsInTheStripfailed: the tab bound for the dock showed in the source stripNewChatDockFlashTests,ChatDockChromeTests,NewChatPlacementTests,ChatColumnPlacementTests,NewTabKindTests,StripNewTabTests,SurfaceTabBarTests,AgentHandlerTests,PageStoreTabTestsVisible change: a new agent chat opens as a docked column on the left with no tab bar, and the bar appears only once that column holds two tabs.
No capture: under the 2026-10-08 rule, captures do not gate merges; the red-then-green tests above are the proof.
Cursor review fixes:
aTabBoundForTheChatDockNeverShowsInTheStrip(the chat showed after its provisional id was rekeyed, and again after another chat docked) andaLoneChatBesideTheChatDockKeepsItsStripChatDockChromeTests,NewChatDockFlashTests,NewChatPlacementTests,ChatColumnPlacementTestsUser-facing strings: none added.
Not run: a review sub-agent (lane rule: no sub-agents).
Base merge (the PR was CONFLICTING): the only conflicts were the generated cmux-tui SDK bindings, because the dock column role landed on base separately. Base's side was taken and the bindings regenerated with
cmux-tui/bindings/codegen/generate.py(--checkclean). After the merge the diff is Swift only.Changelog
Changed: in cmux-next, a new agent chat opens as the docked chat column on the left, and the chat column shows no tab bar until it holds two tabs.
🤖 Generated with Claude Code
Note
Medium Risk
Changes default layout, tab routing, and strip visibility for agent chats with async dock moves; behavior is covered by new tests but affects core workspace UX.
Overview
Agent chats default to a left dock column with no tab strip until that pane holds two tabs. New chats no longer stay as ordinary strip tabs in the focused pane.
NewChatPlacementdecides whether a new agent chat opens in the current pane, joins an existing agent chat dock, or opens hidden in the source pane and then moves into a new left dock viaTabMoves.toNewDockColumn.openAgentTabandAgentTabStore.openTabgain ahiddenflag andpendingDock/dockBoundso dock-bound chats (including after provisional→store rekey) never appear in the source strip during the move.ChatDockChromedriveshidesStriponPaneContentViewand pane snapshots: the chat dock and a lone single-chat screen hide the strip at one tab; a chat beside an existing chat dock keeps its strip.ChatColumnPlacementextracts sharedisChatfor lone-chat detection.Reviewed by Cursor Bugbot for commit 92ee982. Bugbot is set up for automated code reviews on this repo. Configure here.