Skip to content

cmux-next: agent chat is a docked column with no tab bar by default - #18223

Merged
teamleaderleo merged 42 commits into
feat-cmux-nextfrom
next-chat-dock-default
Oct 9, 2026
Merged

teamleaderleo merged 42 commits into
feat-cmux-nextfrom
next-chat-dock-default

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

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_chat dock role).

  • Every new agent chat opens in the chat dock, never as a tab in the strip. That covers New Agent Chat on a pane, the CLI, Duplicate and a session link.
    • If the screen already has a chat dock, the chat becomes a tab there.
    • Otherwise the chat becomes a new left dock with the agent_chat role.
    • A chat alone on its screen keeps a second chat in its own pane, since that chat docks when the first tool opens (cmux-next: tools opened from the docked agent chat open in the strip #18170).
    • A New Tab page that turns into a chat stays where it is.
  • No tab strip by default. The chat dock's pane, and a chat alone on its screen, show no tab strip while they hold one tab. The strip comes back at two tabs.
  • New workspace (decision B, confirmed by the coordinator): Cmd-I makes a new workspace whose chat is alone, full width, with no strip (cmux-next: New Agent Chat opens a new workspace #18137). The daemon refuses a screen whose only column is a dock. The chat docks left on the first terminal, browser or tab.

Not in this PR:

  • One-step move: a new chat in a workspace with tools is still two daemon commands: create it in the focused pane, then move it into the new dock. No flash shows, because the app opens that chat unselected and keeps it out of the source pane's strip until the dock lands, then focuses it in the dock. A dock field on new-conversation-tab that does it in one commit comes after PR4 and PR5, so other clients also see one change.
  • Strip setting: the setting that turns the strip on comes with layout.dockAgentChat and Dock/Undock Chat in the next PR. That PR needs the settings page's locale bundles rebuilt.
  • Web code: none changes. The tab strip is native (PaneContentView), and the agent pane's own header is unchanged.

Testing

Runs use scripts/measure/suite-runner.sh on a fleet runner.

Commit Result
be68974 (red, strip) theChatDockShowsNoStripWithOneTab and aLoneChatShowsNoStrip failed; the two tests that keep the strip passed
027e429 (red, new chat) aNewChatJoinsTheChatDock and aNewChatBesideToolsMakesTheChatDock failed
db5b4dc (green) passed: ChatDockChromeTests, NewChatPlacementTests, ChatColumnPlacementTests, NewTabKindTests, StripNewTabTests, SurfaceTabBarTests, AgentHandlerTests
8588cb8 (red, no flash) aTabBoundForTheChatDockNeverShowsInTheStrip failed: the tab bound for the dock showed in the source strip
2dffd38 (green, no flash) passed: NewChatDockFlashTests, ChatDockChromeTests, NewChatPlacementTests, ChatColumnPlacementTests, NewTabKindTests, StripNewTabTests, SurfaceTabBarTests, AgentHandlerTests, PageStoreTabTests

Visible 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:

Commit Result
c2efdf5 (red) hosted swift test (run 37861441547) fails aTabBoundForTheChatDockNeverShowsInTheStrip (the chat showed after its provisional id was rekeyed, and again after another chat docked) and aLoneChatBesideTheChatDockKeepsItsStrip
d9aedb0 (green), f37a0bc (with #18170's fixes merged) hosted swift test (run 37864903436) passes ChatDockChromeTests, NewChatDockFlashTests, NewChatPlacementTests, ChatColumnPlacementTests
  • The strip hides dock-bound chats through the store's aliases, and a finished dock removes only its own chat.
  • The new chat's focus waits until the layout shows it outside the pane it moved from (no unit test: it needs a live dock move).
  • A chat alone in the strip beside a chat dock keeps its strip.

User-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 (--check clean). 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.

NewChatPlacement decides 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 via TabMoves.toNewDockColumn. openAgentTab and AgentTabStore.openTab gain a hidden flag and pendingDock / dockBound so dock-bound chats (including after provisional→store rekey) never appear in the source strip during the move.

ChatDockChrome drives hidesStrip on PaneContentView and 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. ChatColumnPlacement extracts shared isChat for lone-chat detection.

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

teamleaderleo and others added 15 commits October 6, 2026 18:07
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>
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 49ecced3-42be-4d82-b78f-3012c2de79a0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

…e strip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Stale Bugbot comment from a previous run.

…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>

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

Stale Bugbot comment from a previous run.

…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
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Not verified: macOS jobs did not run: compile and app tests were skipped on 92ee982d67; ci-status is green without them.

CI passes on 92ee982d67 (run 37871967817 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

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

Stale Bugbot comment from a previous run.

teamleaderleo and others added 4 commits October 7, 2026 00:26
…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>
teamleaderleo and others added 3 commits October 7, 2026 03:27
… 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

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

Stale Bugbot comment from a previous run.

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]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a60c68f. Configure here.

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.

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
@blacksmith-sh

This comment has been minimized.

teamleaderleo and others added 2 commits October 8, 2026 14:54
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>

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

Stale Bugbot comment from a previous run.

Comment thread Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/NewChatPlacement.swift Outdated
Comment thread Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/ChatColumnPlacement.swift Outdated
teamleaderleo and others added 2 commits October 8, 2026 15:46
Picks up the base compile fix for AgentPaneDraftStore (#18669, fc10fdf); the last run built against the broken base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the base compile fix for AgentPaneDraftStore (#18669, fc10fdf); the last run built against the broken base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Stale Bugbot comment from a previous run.

teamleaderleo and others added 6 commits October 8, 2026 16:34
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>
teamleaderleo added a commit that referenced this pull request Oct 9, 2026
Takes #18170's and #18223's Cursor fixes (routing, zoom, the chat's
browser context, dock-bound chats hidden across rekey and other docks,
the strip beside the chat dock).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Stale Bugbot comment from a previous run.

teamleaderleo added a commit that referenced this pull request Oct 9, 2026
…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>

@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 using default effort and found 3 potential issues.

There are 4 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 92ee982. Configure here.

var dockBound: Set<String> {
let aliases = services.agentTabs.aliases
return pendingDock.union(pendingDock.compactMap { aliases[$0] })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 92ee982. Configure here.

@teamleaderleo
teamleaderleo merged commit 97b69fb into feat-cmux-next Oct 9, 2026
81 of 94 checks passed
@teamleaderleo
teamleaderleo deleted the next-chat-dock-default branch October 9, 2026 04:22
teamleaderleo added a commit that referenced this pull request Oct 9, 2026
* 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>
teamleaderleo added a commit that referenced this pull request Oct 9, 2026
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>
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