feat(desktop): group projects into folders in the v2 sidebar

AS
2mo ago
+4106-264

Description

Adds a folder level above projects in the v2 dashboard sidebar, so repos can be grouped by context — e.g. Work vs Personal — with a name, colour and collapse.

This is the cross-repo grouping asked for in #4018 (option 1) and #2745, and requested again recently in #4018. It's the piece that per-project sections don't cover: sections group workspaces inside one repo, while this groups the repos themselves. (#1176 was closed as done against sections (#2067), but cross-repo grouping was still missing — hence the recurring requests.)

Naming: deliberately "folder", not "group". The UI already calls a section of workspaces inside a project a "group" ("New workspace group", "Rename group"), so a second meaning would be ambiguous — and that ambiguity is exactly what caused #1176 to be closed prematurely. #2745's title offers "folder" as the alternative. The menu labels now name the level each action applies to, because the two sat close enough together to read as one feature.

What's included

  • New local collection v2SidebarFolders (name, color, isCollapsed, tabOrder), mirroring the existing v2SidebarSections one level down; a project points at its folder via a nullable folderId, exactly like a workspace points at its section via sidebarState.sectionId.
  • Folder header: chevron collapse, inline rename, and a right-click / hover "..." menu (rename, set colour, set icon, delete) — same interaction vocabulary as section headers. Folder identity is an emoji/image icon when set, falling back to a small colour dot; the folder colour also tints a thin rail that runs down the folder’s contents, so it’s visible where a folder ends (no divider needed before the ungrouped list). The project count shows only while collapsed.
  • Move to folder submenu on a project's context menu, with New folder… and Remove from folder.
  • New project folder in the PROJECTS header menu, so a folder can be created before any repo exists.
  • Deleting a folder returns its repos to the sidebar root — it never deletes projects. A project pointing at a missing folder falls back to the root, so a repo can't become unreachable.

Purely renderer-local state (localStorage, org-scoped), consistent with how sidebar placement already works — no schema, host-service or API changes.

Related Issues

Related to #4018 (implements the cross-repo grouping slice), #2745, and #1176.

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Unit tests (folderMutations.test.ts, 11 cases) cover create / rename (incl. trimming and blank-rejection) / collapse / colour / move in and out / reorder, plus the two safety cases: deleting a folder re-parents its projects to the root, and other folders' projects are untouched.

Verified end-to-end in the running app (dev build, driven over CDP):

  1. PROJECTS → New project folder → folder created and drops straight into inline rename; renamed to Work; persisted to v2-sidebar-folders-<org>.
  2. Created a second folder Personal — both render with their own colour and project count.
  3. Clicking a folder header toggles collapse, persisted per folder (Work: true, Personal: false).
  4. Right-clicked a project → Move to folder → Work: the project's count moved 0 → 1, the repo left the root list, and expanding Work shows it nested underneath.

bun run typecheck, Biome, and bun test (2302 desktop tests) are green.

Screenshots (if applicable)

Sidebar with Work (purple, containing acme-web) and Personal (green, empty). Happy to attach the captures if useful.

Additional Notes

Scoped deliberately small: assignment is via the context menu, not drag-and-drop, so the diff stays reviewable and doesn't touch the project DnD that sort/filter (#5956) and pinning (#5963) just landed on. Drag-a-repo-into-a-folder is the obvious follow-up if you want it.

Two things worth a maintainer's call:

  • Naming — "folder" vs "group" (see above). Happy to switch if you'd rather rename the workspace-level concept instead.
  • State ownership — this follows today's renderer-local pattern. #5822 (client-owned sidebar state) and #5756 (host-owned sections) are both in flight; whichever lands, folder state should migrate with the rest of the sidebar rather than needing its own path. Glad to rebase onto either.

Open in Stage

Summary by cubic

Groups projects into collapsible collections in the v2 sidebar so repos can be organized by context (named "collection" because "group" already means workspace sections and "folder" means on-disk directories). Previously all projects sat at the root; now each project can sit in a collection or at the root, and deleting a collection returns its projects to the root.

  • Data/persistence: adds renderer-local v2SidebarCollections and a nullable collectionId on projects; registers v2-sidebar-collections-*, retires the pre-rename v2-sidebar-folders-* via DEAD_KEYS, hardens the collection, re-sorts in JS, and heals legacy collectionId: undefined to null. No host/API changes.
  • Collection UI: collapse/expand, inline rename, color, and icon (emoji or 64px data URL); minimal header with a color dot and tinted content rail; count shows only when collapsed; header is a single tab stop with accessible emoji choices.
  • Move projects: PROJECTS header adds "New collection"; project context menu adds "Move to collection" with "New collection…" and "Remove from collection", with labels distinguishing workspace groups from project collections. Drag projects onto collection headers, the root drop zone, or another project to adopt its collection; pointer-first collision detection with a root zone that expands while dragging.
  • Bulk selection: Cmd/Ctrl- and Shift-click select projects; a bulk toolbar moves selected projects into a collection, creates one, or removes them from collections. Project selection is exclusive with workspace selection; Escape clears.
  • Tests/i18n: unit tests cover collection lifecycle, moves, ordering, grouping, and fallbacks (deleted/missing collections return projects to root); collection strings are translated across all 16 locales with ICU plurals.

Summary by CodeRabbit

  • New Features
    • Organize dashboard projects into collapsible, colorable folders with drag-and-drop, including a root drop zone for moving projects out of folders.
    • Add folder actions (create, inline rename, delete, color, collapse/expand) and project “Move to folder” options (including “Remove from folder” to return to root).
    • Allow reordering within the sidebar while re-parenting via drop targets.
  • Bug Fixes
    • Projects with missing/invalid folder assignments (or after folder deletion) automatically fall back to the root list.
  • Tests
    • Expanded coverage for folder lifecycle rules, colors, collapse state, project moves, deletion, ordering, and sidebar grouping behavior.