Skip to content

Add sidebar folder grouping - #3988

Closed
austinywang wants to merge 14 commits into
mainfrom
issue-2522-sidebar-folder-grouping
Closed

austinywang wants to merge 14 commits into
mainfrom
issue-2522-sidebar-folder-grouping

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #2522

Summary

  • group sidebar workspaces by initial directory with collapsible folder headers
  • show pinned workspaces in a bookmarks section and support dropping workspaces onto bookmarks/folder headers
  • persist mutable workspace grouping directories separately from live current directory

Testing

  • Not run locally per repository policy. Added behavior coverage for grouping, group moves, bookmark moves, observation, and session persistence.

Note

Medium Risk
Touches core sidebar ordering, drag-and-drop, multi-select, and session persistence; regressions could mis-order workspaces or lose grouping on restore, though behavior is covered by new unit tests.

Overview
The sidebar now groups unpinned workspaces by initialDirectory, with collapsible folder headers (shown when there is more than one folder group), a BOOKMARKS block for pinned workspaces, and an Add Folder control that opens a directory picker to spawn a workspace in that path.

Selection, shift-range, Move Up/Down, and tab drag-and-drop use the rendered workspace order (respecting collapsed groups) instead of raw TabManager.tabs order. Drops on bookmark or folder headers move workspaces into those segments; row drops can change folder via moveWorkspaceInSidebarVisualOrder.

initialDirectory is stored on each workspace and persisted in session snapshots separately from currentDirectory (with restore and autosave hashing updates). List rendering and reorder logic live in SidebarWorkspaceList / SidebarWorkspaceListRenderPolicy, with grouping planned in SidebarWorkspaceGroupingPlanner.

New EN/JA strings cover bookmarks and add-folder UI. Tests cover grouping, rendered IDs, group/bookmark moves, sidebar observation, and session restore of initialDirectory.

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


Summary by cubic

Adds folder-based grouping to the sidebar with collapsible headers, a BOOKMARKS section, and an Add Folder button, all using the visual (rendered) order for selection and drag-and-drop. Persists a per-workspace initialDirectory separate from currentDirectory (addresses Linear #2522).

  • New Features

    • Group workspaces by initialDirectory with collapsible headers; headers show only when multiple groups; the selected group auto-expands.
    • BOOKMARKS and folder headers accept drops; items move to the end of that group/bookmarks. Add Folder opens a directory picker (EN/JA).
    • Persist and restore initialDirectory separately; included in autosave hashing.
    • New TabManager APIs for visual-order moves and group/bookmarks moves; extracted SidebarWorkspaceList and render policy for clean grouped rendering.
  • Bug Fixes

    • Selection, shift-range, Move Up/Down, and drag planning use the rendered order and respect collapsed groups; Move Up/Down disables when blocked by group boundaries.
    • Drops use previous rendered neighbor and first/last rendered rows; header drops clear indicators; empty-area drops correctly detect “end,” including with collapsed groups.
    • Bonsplit tab moves use visual indexes and support insertion at the planned index or into a new workspace.

Written for commit 13269a8. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added workspace folder grouping to the sidebar, organizing workspaces by directory
    • Introduced collapsible folder groups for better workspace organization
    • Added ability to create and manage bookmarked workspaces
    • Enhanced sidebar with folder headers and management controls
    • Improved localization support for sidebar UI elements (English and Japanese)
  • Tests

    • Added test coverage for workspace session persistence and sidebar grouping logic

Review Change Stack

@vercel

vercel Bot commented May 12, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 26, 2026 10:25am
cmux-staging Building Building Preview, Comment May 26, 2026 10:25am

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements workspace sidebar grouping by directory with collapsible folder sections and a pinned bookmarks area. It adds a mutable initialDirectory property to workspaces, introduces a grouping planner that organizes workspaces by pinned status and directory, and restructures the sidebar UI with folder headers, collapse toggles, and cross-group drag-and-drop support.

Changes

Sidebar Workspace Folder Grouping

Layer / File(s) Summary
Grouping model and planner
Sources/Sidebar/SidebarDropPlanner.swift, cmuxTests/SidebarWorkspaceDropPlannerTests.swift
SidebarWorkspaceGroupingInput, SidebarWorkspaceFolderGroup, SidebarWorkspaceGroupingPlan, and SidebarWorkspaceGroupingPlanner define the data structures and computation logic for grouping workspaces by pinned status and normalized directory path. Tests verify pinned/unpinned separation, folder grouping by directory, and fallback directory handling for blank paths.
Workspace initialDirectory property, observation, and tests
Sources/Workspace.swift, cmuxTests/WorkspaceUnitTests.swift
Workspace gains a mutable @Published var initialDirectory initialized from workingDirectory and included in sidebarObservationPublisher to trigger sidebar re-renders on directory changes. Tests verify sidebar observation publisher emits on initialDirectory changes without duplicating emissions for identical assignments.
Session snapshot persistence and restoration
Sources/SessionPersistence.swift, Sources/Workspace.swift, cmuxTests/SessionPersistenceTests.swift
SessionWorkspaceSnapshot adds optional initialDirectory field; snapshot creation stores it, restoration normalizes and restores it with fallback to currentDirectory. Tests verify snapshots preserve initialDirectory separately from currentDirectory.
TabManager workspace movement APIs and visual ordering
Sources/TabManager.swift, cmuxTests/WorkspaceUnitTests.swift
New public methods set initialDirectory, move workspaces to folder group ends, move pinned workspaces to bookmark ends, and perform sidebar visual-order moves with optional directory transfer; internal helpers compute insertion indices and leverage the grouping planner. Session autosave fingerprinting updated to hash initialDirectory instead of currentDirectory. Tests validate group-aware moves, no-op detection, and sidebar visual ordering preservation.
Sidebar render context with grouping plan
Sources/ContentView.swift
WorkspaceListRenderContext extends to include groupingPlan, workspaceById, and computed renderedWorkspaceIds and workspaceOrderSignature reflecting the current grouped and collapsed state.
Sidebar UI restructuring with folder groups and bookmarks
Sources/ContentView.swift
Sidebar rendering restructured to iterate over bookmarks and folder groups; introduces collapsedGroups state, folder collapse toggles, and new header/divider components (SidebarBookmarkHeader, SidebarGroupHeader, SidebarWorkspaceSectionDivider, SidebarAddFolderButton). Updates drop overlay logic to use rendered/visible workspace IDs.
TabItemView visual ordering parameters
Sources/ContentView.swift
TabItemView extended with previousRenderedWorkspaceId, workspaceOrderSignature, and closures for computing visual/rendered workspace ID lists; updated equatability includes these fields.
Selection index and reordering with visual indices
Sources/ContentView.swift
lastSidebarSelectionIndex derivation switched to use sidebarVisualIndex from grouping plan; moveBy() and shift-selection rewritten to operate on visual IDs; context-menu move-button disabling uses visual ordering.
Drag-and-drop with visual ordering and group transfer
Sources/ContentView.swift
SidebarTabDropDelegate.performDrop rewritten to plan moves via visual indices, detect initial-directory group changes, and commit via moveWorkspaceInSidebarVisualOrder; drop delegates for folder/bookmark headers added; drop indicator planning and selection syncing updated for visual indices.
Localization strings for sidebar UI
Resources/Localizable.xcstrings
Added "Add Folder" button, prompt, title, tooltip, and "BOOKMARKS" section strings in English and Japanese.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • manaflow-ai/cmux#3152: Introduces WorkspaceListRenderContext-driven sidebar rendering and scroll/visibility behavior that directly overlaps with this PR's render-context structure and sidebar reordering.
  • manaflow-ai/cmux#1218: Modifies sidebar/tab UI and workspace selection plumbing (ContentView/TabItemView/TabManager), overlapping at the tab selection and reordering logic level.
  • manaflow-ai/cmux#2405: Modifies Workspace sidebar observation and TabItemView subscription wiring to scope sidebar-visible updates, directly related to this PR's sidebarObservationPublisher changes.

Poem

A rabbit hops through directories neat, 📁
Bookmarks pinned, each folder's feat,
Drag them round in visual grace,
Collapsed sections save the space! ✨
Sidebar grouped, a tidy retreat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add sidebar folder grouping' directly and concisely summarizes the main feature introduced in the changeset—folder-based grouping for sidebar workspaces.
Linked Issues check ✅ Passed The PR fully implements all coding objectives from #2522: mutable initialDirectory, folder grouping logic, collapsible headers, bookmarks section, drag-and-drop between groups, session persistence, and required UI strings.
Out of Scope Changes check ✅ Passed All changes are scoped to the folder grouping feature: sidebar rendering updates, workspace grouping planner, persistence layer, TabManager reordering APIs, and supporting tests—no unrelated modifications detected.
Description check ✅ Passed The PR description covers the required template sections: summary of changes and why, testing approach with new unit tests, and a comprehensive overview with medium-risk assessment.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2522-sidebar-folder-grouping

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds folder-based grouping to the sidebar: unpinned workspaces are grouped by initialDirectory under collapsible folder headers, while pinned workspaces appear in a dedicated BOOKMARKS section. A new SidebarWorkspaceList view encapsulates this layout together with drag-drop delegates, and three new TabManager APIs (moveWorkspaceInSidebarVisualOrder, moveWorkspaceToInitialDirectoryGroupEnd, moveWorkspaceToBookmarksEnd) drive all reorder and group-change operations.

  • Workspace gains a new @Published var initialDirectory that is persisted and restored separately from currentDirectory, allowing the sidebar grouping to survive session restores without being affected by live navigation.
  • Selection, shift-range, Move Up/Down, and every syncSidebarSelection path now use the grouped visual order (including collapsed-group awareness) rather than the raw tabs array index.
  • All new user-facing strings (sidebar.bookmarks.title, sidebar.addFolder.*) are fully translated for both supported locales (EN and JA).

Confidence Score: 5/5

Safe to merge — all selection, drag-drop, and session-persistence paths are correctly updated to use the new visual order, and no functional regressions were found.

The new TabManager move APIs correctly guard @published writes behind changed-flag checks, the session snapshot round-trips initialDirectory separately from currentDirectory with a safe nil-default for old sessions, and every lastSidebarSelectionIndex sync site was updated to use the grouped visual order. The only non-critical observation is the intentional switch from LazyVStack to VStack that removes a performance comment without leaving an explanation for future contributors.

Sources/Sidebar/SidebarWorkspaceList.swift — the VStack/LazyVStack trade-off should be documented inline.

Important Files Changed

Filename Overview
Sources/Sidebar/SidebarWorkspaceList.swift New 720-line file extracting sidebar rendering logic: workspace list view with folder group headers, bookmark section, add-folder button, and all drop delegates. Switches from LazyVStack to VStack for row rendering.
Sources/TabManager.swift Adds moveWorkspaceInSidebarVisualOrder, moveWorkspaceToInitialDirectoryGroupEnd, moveWorkspaceToBookmarksEnd, setWorkspaceInitialDirectory, and normalizedInitialDirectory. Correctly guards @published writes behind changed-flag checks. Autosave hash now includes initialDirectory.
Sources/Sidebar/SidebarDropPlanner.swift Adds grouping types (SidebarWorkspaceGroupingInput, SidebarWorkspaceFolderGroup, SidebarWorkspaceGroupingPlan, SidebarWorkspaceGroupingPlanner) alongside existing drop planner. Logic is straightforward and correctly normalizes directories.
Sources/Workspace.swift Adds @published var initialDirectory, wires it into sidebarObservationPublisher, sets it in the initializer, and restores it from session snapshots (falling back to currentDirectory when absent).
Sources/ContentView.swift Replaces local WorkspaceListRenderContext struct and workspaceRows() with SidebarWorkspaceList, adds collapsedGroups @State, updates all sidebarSelectionIndex syncs to use visual order, and adds onChange handlers to reveal and sync selection when grouping changes.
Sources/SessionPersistence.swift Adds optional initialDirectory field to SessionWorkspaceSnapshot with nil default for backwards-compatible decoding.
Resources/Localizable.xcstrings Adds five new sidebar strings (addFolder.button/panelPrompt/panelTitle/tooltip and bookmarks.title), each with EN and JA translations. All supported locales are covered.
cmuxTests/WorkspaceUnitTests.swift Adds seven new TabManager move-behavior tests and one sidebarObservationPublisher test. All test the no-op/publish-count contracts introduced by this PR.
cmuxTests/SessionPersistenceTests.swift Adds one session-persistence test confirming initialDirectory round-trips separately from currentDirectory.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[TabManager.tabs] -->|SidebarWorkspaceGroupingPlanner.plan| B[SidebarWorkspaceGroupingPlan]
    B --> C[bookmarkIds]
    B --> D[folderGroups: SidebarWorkspaceFolderGroup]
    D -->|collapsedGroupIds| E[renderedWorkspaceIds]
    C --> E
    E --> F[SidebarWorkspaceList body]
    F --> G[SidebarBookmarkHeader + rows]
    F --> H[SidebarGroupHeader + rows]
    F --> I[SidebarAddFolderButton]
    F --> J[SidebarBonsplitTabWorkspaceDropOverlay]
    G -->|drop| K[moveWorkspaceToBookmarksEnd]
    H -->|drop| L[moveWorkspaceToInitialDirectoryGroupEnd]
    H -->|row drop| M[moveWorkspaceInSidebarVisualOrder]
    I -->|pick dir| N[addWorkspace workingDirectory]
    K --> A
    L --> A
    M --> A
    N --> A
    A -->|sessionSnapshot| O[SessionWorkspaceSnapshot]
    O -->|initialDirectory field| P[restoreSessionSnapshot]
    P --> A
Loading

Reviews (6): Last reviewed commit: "merge: sync issue 2522 with main" | Re-trigger Greptile

Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/TabManager.swift
Comment thread Sources/TabManager.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift
Comment thread Sources/TabManager.swift
Comment thread cmuxTests/WorkspaceUnitTests.swift
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/TabManager.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
Sources/ContentView.swift (1)

9434-9563: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract the new grouped-sidebar UI and drop logic out of ContentView.swift.

This adds several new responsibilities to an already oversized production file: grouped rendering, folder headers, add-folder UI, and header-specific drop delegates. Please move these sidebar-specific types/helpers into dedicated files under Sources/ before merge.

As per coding guidelines, do not accept more than 250 lines added to an existing production Swift file that is already over 800 lines.

Also applies to: 11890-12016, 14576-14672

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/ContentView.swift` around lines 9434 - 9563, The ContentView.swift
file now contains new grouped-sidebar UI and drop logic that should be moved
into focused files; extract the workspaceRows view and its helpers
(workspaceRows, renderFolderGroups, WorkspaceFolderRenderGroup) plus the related
custom views/delegates (SidebarBonsplitTabWorkspaceDropOverlay,
SidebarGroupHeader, SidebarBookmarkHeader, SidebarAddFolderButton and any
drop-related closures or preference keys) into one or more new files under
Sources/ (e.g. Sidebar/SidebarWorkspaceList.swift and
Sidebar/SidebarDropOverlay.swift). Ensure you preserve all external dependencies
(tabManager, draggedTabId, selectedTabIds, collapsedGroups,
dragAutoScrollController, dropIndicator, WorkspaceListRenderContext types) by
keeping their interfaces intact and replacing the moved code in
ContentView.swift with a small wrapper call to the new Sidebar workspace view.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/ContentView.swift`:
- Around line 9565-9571: toggleCollapsedGroup(_:) currently allows collapsing a
group that contains the active tab, hiding the selected workspace; update this
function to check tabManager.selectedTabId against the group's member IDs and
prevent collapsing (or remove the group from collapsedGroups) if the selected
tab is inside that group, and additionally ensure any code that changes
tabManager.selectedTabId auto-expands its containing group by removing that
groupId from collapsedGroups; reference toggleCollapsedGroup(_:),
collapsedGroups, and tabManager.selectedTabId to locate where to add the guard
and where to auto-expand on selection changes.
- Around line 14833-14849: The current
sidebarVisualIndex/sidebarVisibleWorkspaceIds functions derive visible IDs from
SidebarWorkspaceGroupingPlanner.plan(...).visibleWorkspaceIds which ignores
folder collapse state, allowing lastSidebarSelectionIndex anchors to point at
hidden workspaces; update sidebarVisualIndex to compute visible/rendered
workspace order using the actual rendered order (e.g. the same source used by
VerticalTabsSidebar for expansion/collapse state) or accept that rendered IDs as
an input, and if the workspaceId is not present in that rendered list
clear/return nil so callers (and lastSidebarSelectionIndex logic) never anchor
to a hidden workspace; reference sidebarVisualIndex, sidebarVisibleWorkspaceIds
and lastSidebarSelectionIndex/VerticalTabsSidebar when making the change.

In `@Sources/TabManager.swift`:
- Around line 4066-4070: The current branch that handles nextInitialDirectory ==
workspace.initialDirectory sets insertionIndex = min(currentIndex,
remainingTabs.count), which can place a moved pinned workspace before other
pinned items and later survive an unpin, breaking pinned-first ordering; change
that branch to clamp the computed index to be no less than pinnedCount (e.g.
insertionIndex = max(min(currentIndex, remainingTabs.count), pinnedCount)) so
moved items in the same directory will always be inserted after the pinned
block, referencing nextInitialDirectory, workspace.initialDirectory,
insertionIndex, currentIndex, remainingTabs, and pinnedCount.

---

Outside diff comments:
In `@Sources/ContentView.swift`:
- Around line 9434-9563: The ContentView.swift file now contains new
grouped-sidebar UI and drop logic that should be moved into focused files;
extract the workspaceRows view and its helpers (workspaceRows,
renderFolderGroups, WorkspaceFolderRenderGroup) plus the related custom
views/delegates (SidebarBonsplitTabWorkspaceDropOverlay, SidebarGroupHeader,
SidebarBookmarkHeader, SidebarAddFolderButton and any drop-related closures or
preference keys) into one or more new files under Sources/ (e.g.
Sidebar/SidebarWorkspaceList.swift and Sidebar/SidebarDropOverlay.swift). Ensure
you preserve all external dependencies (tabManager, draggedTabId,
selectedTabIds, collapsedGroups, dragAutoScrollController, dropIndicator,
WorkspaceListRenderContext types) by keeping their interfaces intact and
replacing the moved code in ContentView.swift with a small wrapper call to the
new Sidebar workspace view.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c6db4e28-6f41-49a8-b96f-3fb1c12b575f

📥 Commits

Reviewing files that changed from the base of the PR and between 5829da2 and fb7d732.

📒 Files selected for processing (9)
  • Resources/Localizable.xcstrings
  • Sources/ContentView.swift
  • Sources/SessionPersistence.swift
  • Sources/Sidebar/SidebarDropPlanner.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • cmuxTests/SessionPersistenceTests.swift
  • cmuxTests/SidebarWorkspaceDropPlannerTests.swift
  • cmuxTests/WorkspaceUnitTests.swift

Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/TabManager.swift
…lder-grouping

# Conflicts:
#	Sources/ContentView.swift
#	Sources/Workspace.swift
#	cmuxTests/WorkspaceUnitTests.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.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

There are 5 total unresolved issues (including 2 from previous reviews).

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 caad618. Configure here.

Comment thread Sources/Sidebar/SidebarWorkspaceList.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/TabManager.swift
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 13269a83 Deployed May 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sidebar workspace folder grouping with drag-and-drop, collapsible sections, and bookmarks

3 participants