Skip to content

Sidebar: categorize and group fleet and agent sessions - #28062

Draft
teamleaderleo wants to merge 26 commits into
feat-cmux-nextfrom
lane/ux-sidebar-categories
Draft

teamleaderleo wants to merge 26 commits into
feat-cmux-nextfrom
lane/ux-sidebar-categories

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

HOLD: structural sidebar change, waiting on a dogfood build

Leo said (2026-10-30 ~04:35Z): "i feel like there's still a big mess of uncategorized stuff in the sidebar", "i mean we dont want to be too opinionated by defgault", and "im saying for my particular sidebar there's a lot of wack stuff there".

Turn on Settings > Sidebar > Session Tree to group existing agent-session workspaces under foldable host headers. The setting remains off by default. Host identities come from existing tab AgentSessionRef values through SidebarSessionTreeFeed; the production SidebarLayout.make path groups the rows. No agent-open metadata or title/subtitle changes.

The window's projected workspace rows determine whether a lineage parent is present. Loose children follow their live parent, including a parent in a user group; explicit child placement stays placed. Pinned/custom boundaries and visible machine headers remain boundaries. Cross-machine lineage can follow a parent when neither machine section draws a header. With no parent here, a session becomes a root in its host group. Host badges count folded descendants once. Local-host aliases remain local; workspaces with mixed local/fleet tabs have a placement follow-up below.

While host headers show, pointer reordering inside the sidebar is refused on legacy and document paths; the lifted card is dimmed and no gap or model move occurs. Drag-out remains available. External tabs may still drop into an existing workspace, but cannot create workspace/group insertion slots in the grouped layout. Grouped reorder and keyboard parity remain follow-ups. Existing keyboard folds and sole-member group behavior are preserved.

Design: leo-workspace design/sidebar.md (category homes and lane grouping, #1666 and #1684 in that repo). SidebarCategory is an audit taxonomy: its exhaustive SidebarDocBody switch is the compiler gate for known kinds, and tests check category assignments and stored placements. Unknown kinds without placement are a future wire-format requirement, not implemented here: current Swift wire records require a section and Rust rejects unknown bodies.

Validation at a4d09dab84aa5cbed9c9e3dff64e1204d752b6eb: independent Opus 5.5 medium SHIP; fleet Linux app compile passed (job 1e6c8a8dc09833fd3dabc0a2, reported compile time 94 s); git diff --check passes. Focused category, host projection and integration suites are running in fleet step 5e7a18b42ee64d19542d44a2; display-dependent coverage is advisory. The rebase preserves the retired sidebar tab-row removal, current tab-drop API, keyboard-fold focus and sole-member groups. Dogfood validation remains outstanding, so the HOLD stays.

Nonblocking review follow-ups, owned by this PR's originating lane:

Risk: structural row projection, fold/selection identity and drag behavior. The projection, its view integration and tests stay together so the behavior can be reviewed as one change. Screenshots are a follow-up, not a landing gate; the busy-sidebar flow still needs to be tried in the dogfood build.

agent session

@blacksmith-sh

blacksmith-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Found 4 test failures on Blacksmith runners:

Failures

Test View Logs
main.DeveloperIdAppTests.test_download_prints_the_active_profile_only/
test_download_prints_the_active_profile_only
View Logs
stubs: /System/Library/Frameworks/Security.framework/Versions/A/Security has no stub/
Security has no stub
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_CFRetain']/
CoreFoundation, missing 1: ['_CFRetain']
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_log2']/
CoreFoundation, missing 1: ['_log2']
View Logs

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

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 30, 2026
@teamleaderleo teamleaderleo added media-tour-mac media-tour Run the cmux-next UI tour (cmux-next-media.yml) on every push hold cmux-next batch queue: keep this PR out of the batch (docs/ci/cmux-next-batch.md) labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Default changes

This pull request changes 1 default at a4d09dab84aa (what everyone gets without configuring anything):

  • tunable-source.CmuxNextSidebar/Logic/SidebarLayout.swift:make: "af56e0028b137f10cae02e65c0f97687fd07e671d9d7192363ea6cc604b6133b" -> "4157b095d27528d7c96137d7154979716a044a6f09e86f52ec30246a1e3517f3"

Signed off on this commit by review (subagent claude-opus-5-5 medium).

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

@teamleaderleo
teamleaderleo force-pushed the lane/ux-sidebar-categories branch from f38c66b to 3775c8e Compare October 30, 2026 16:20
teamleaderleo and others added 8 commits November 1, 2026 01:38
…ouping

Code (parentAndHostFoldsKeepTheWholeBadgeCount): the base projection
already drops the loose descendants of a folded parent, so groupingSessions
saw a childless parent, wiped its .collapsed chevron and counted one row in
the host badge. It now carries those folded descendants as undrawn entries,
so the fold, roll-up and badge count the whole family.

Code (looseChildFollowsVisibleParentInsideAnExplicitUserGroup): when no
host header was drawn (the parent sits in a user group), groupingSessions
fell back to the base projection and threw away the cross-list nesting it
had just built. It now keeps its result whenever lineage nested a row the
base drew as a root.

Test (hostGroupRefusalMatrixRunsLegacyAndDocumentPaths): the matrix passed
list coordinates as window points. The list is flipped, so the external
drop aimed at "plain" landed below the last row (a new-workspace slot the
host gate refuses). Points now go through list.convert(_:to: nil), as the
other sidebar GUI tests do.

Sidebar-Look: no visual change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
With per-computer headers drawn, groupingSessions nested a child from a
later machine section under its parent recursively, so the child drew
under the previous machine's header. A child now follows a parent in
another section only when neither section draws a header (one list).
Adds a non-flattened, multi-section layout test.

Sidebar-Look: no visual change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
… groups

debug.sidebar_nested_fixture gains a sessionHosts scenario: the needsYou
rows and lineage with synthetic fleet hosts on their agent tabs, so a
capture shows the host headers. Its before is the existing needsYou
scenario (the same rows, no hosts), which the merge-base also has.

Sidebar-Look: no visual change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
@teamleaderleo
teamleaderleo force-pushed the lane/ux-sidebar-categories branch from abf8554 to 47a402f Compare November 1, 2026 01:39
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at a4d09da

Read code only: git diff b85875a4be9a..a4d09dab (merge base b85875a, which includes 8a3af6b).

What the lane asked me to check:

  • Base's keyboard fold and sole-member behaviour stay as they are. The PR doesn't touch SidebarGroupKeys, SidebarLayout+SoleMember or SidebarFoldAction. groupingSessions copies each row as it is, so standsForGroup and group carry through. A sole-member row has group != nil, so it never goes into a host bucket. It can still be a lineage anchor (it is in machineParents), which the PR body describes. Done and Show more stay keyboard stops and still toggle through focusFold. The keyboard stops still read the drawn rows, and a .sessionHost row adds no stop.
  • No retired tab-row APIs. The diff adds no reference to SidebarRowKey.tab, SidebarTabDisclosure, tabKind/tabDisclosure, tabRowHeight, showWorkspaceTabs, collapsedWorkspaces, listsTabs or toggleWorkspaceTabs. The only .tab it adds is SidebarDocBody.tab in SidebarCategory, which is not retired. The tab drop uses the current resolveTabDrop(y:base:sections:sourceMachine:) and SidebarTabDrop.intoWorkspace.
  • Correctness. .sessionHost is handled in every exhaustive SidebarRowKey switch I could find: RustRowKey, resolvable, EmptyArea, Viewport, DocDrag, InlineRename, ContextMenu, Drag, Mouse, RowViewPool and Layout configure. The Rust resolver never receives it. Host fold keys stay consistent: the bare host id goes into collapsedHosts and the section-suffixed key is the row id, with hostID(from:) mapping between them. The reordered drag checks don't change behaviour when no host headers show: offerHandoff needs the pointer beyond the sidebar's side edges, holdsDragOut needs an .item payload and the pin drop needs .workspaces, so moving the pin check later changes nothing. The setting stays off by default.

Not verified here: the fleet compile and swift test jobs were still pending when I read the PR.

Findings (none blocking):

  1. [follow-up] Sources/CmuxNextSidebar/Views/SidebarGroupKeys.swift:27: stops has no .sessionHost stop, so a host bucket can't be focused or folded from the keyboard. Only the mouse and VoiceOver (AXPress) can fold it.
  2. [follow-up] Sources/CmuxNextSidebar/Model/SidebarModel.swift:547: Cmd-Opt-Up/Down keyboard reorder isn't refused while host headers show, unlike pointer drags. It moves the document by document slots, which the grouped projection may not show as a visible step. This is opt-in only and loses no data.
  3. [follow-up] Sources/CmuxNextAgentLanes/SidebarSessionTreeFeed.swift:159: a workspace that has both local and fleet agent tabs goes into the first fleet host's bucket. It may belong in the local list.
  4. [follow-up] Sources/CmuxNextSidebar/Model/SidebarSessionTree.swift:211: reveal runs for the restored selection at launch and on every selection change, so a collapsed host holding the selected workspace reopens on the next launch.
  5. [follow-up] Sources/CmuxNextSidebar/Logic/SidebarLayout+SessionHosts.swift: about 200 lines of new projection logic in Swift: bucketing, lineage re-parenting and gap/anchor remapping. The rulebook asks for new logic like this in Rust, so it should move there with the grouped-reorder follow-up.
  6. [follow-up, nit] Sources/CmuxNextAgentLanes/SidebarSessionTreeFeed.swift:208: if localHost == nil { state.hosts.removeAll() } does nothing, because hosts are only filled when localHost is set. Sources/CmuxNextSidebar/Logic/RustSidebarRow.swift:190 has a stray blank line.

Follow-ups:

  • Make host headers keyboard stops, with Space/Return to fold them.
  • Refuse keyboard reorder while host headers show, or land grouped reorder.
  • Choose the bucket for workspaces with mixed local and fleet tabs.
  • Stop reopening collapsed hosts for the restored selection at launch.
  • Move the host-grouping projection to Rust.
  • Remove the redundant removeAll and the blank line.

@teamleaderleo teamleaderleo left a comment

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.

W2-11 source review, pinned to a4d09dab84aa5cbed9c9e3dff64e1204d752b6eb (draft/HOLD respected; this is an ergonomic risk and test recommendation, not an approval or merge request).

The opt-in host grouping is promising, particularly the one-row-per-session invariant and preservation of document IDs. The global drag gate here has a broader effect than the host projection needs: when one fleet host header is visible anywhere, every SidebarListView workspace/group/item drag is marked refused, including a local workspace and manually ordered user group in an unrelated machine section. updateDrag then exits before drop resolution (line 121), and finishDrag cancels (line 144). Drag-out stays possible.

The PR body explicitly describes this temporary policy, and #29989 tracks keyboard parity. The product acceptance test should go further than making Cmd-Opt-Up/Down refuse too: teamleaderleo/leo-workspace/design/sidebar.md ("Category homes and lane grouping", plus "What nests") preserves manual sibling order and says a grouped drag should map drawn neighbors to document insertion slots. For someone with 100 chats, enabling Session Tree must not disable reorganizing unrelated local tasks.

Suggested bounded next slice for the originating owner: allow source/target pairs wholly inside an unaffected document list (local rows, an explicit user group, or custom section); refuse only host-projected/root-crossing/ambiguous insertion slots. Preserve DropPosition document identity and the existing drag-out escape. Add two gestures or integration tests: (1) with a fleet header elsewhere, drag two plain local workspaces in the same section and verify one persisted reorder; (2) drag a host-grouped root across hosts and verify clear refusal, no daemon/document mutation. Pair each with Cmd-Opt parity. If the intentionally broad refusal remains through dogfood, call it out as a feature-level restriction to evaluate before enabling Session Tree by default.

No macOS interaction run was possible here; this finding is traced through the actual guard and return paths.

)
drag.grabOffsetX = press.point.x - rowFrame.minX
drag.standingWorkspace = standing
drag.refusedAtBegin = displayed.hostHeadersShowing

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.

displayed.hostHeadersShowing is global for the entire list: one remote host bucket disables even unrelated local workspace and explicit-group reorders (then update returns at line 121 and finish cancels at 144). #29989 covers keyboard parity, but a source/target-scoped gate would retain the manual order promised by design/sidebar.md. Please add a mixed-host/local drag regression before graduating the opt-in; keep cross-host slots refused and preserve drag-out.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over.


For awareness, from the asks pass: two of Leo's asks wait on this PR.

  • 2026-10-30, on lane sessions in the sidebar: "well no just make it more organized by defautl maybe idk". After dogfood, grouping should be on by default, not behind Session Tree (off), and this PR's body should quote that line.
  • 2026-10-23: "mostly i would like to be able to see all running sessions on my sidebar. there's a lot of nonsense rn and a lot of maybe dead maybe alive stuff." Running sessions on other machines still don't show by default: RemoteMachinesSidebar is unbound in SidebarSectionHandlers.swift.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — a4d09dab Deployed Nov 1, 2026 by teamleaderleo via wait for same-key tree publisher #92853
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold cmux-next batch queue: keep this PR out of the batch (docs/ci/cmux-next-batch.md) media-tour Run the cmux-next UI tour (cmux-next-media.yml) on every push media-tour-mac risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant