Skip to content

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

Open
ashbrener wants to merge 40 commits into
ParadevAI:mainfrom
ashbrener:sidebar-project-groups
Open

ashbrener wants to merge 40 commits into
ParadevAI:mainfrom
ashbrener:sidebar-project-groups

Conversation

@ashbrener

@ashbrener ashbrener commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

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:


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.

Written for commit f6d3e19. Summary will update on new commits.

Review in cubic

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.

Adds a folder level above projects in the dashboard sidebar so repos can be
organised by context (e.g. work vs personal), addressing the cross-repo
grouping ask in ParadevAI#4018 / ParadevAI#2745 that per-project sections don't cover.

- New local collection v2SidebarFolders (name, colour, collapse, order),
  mirroring the existing per-project sections; projects point at a folder via
  a nullable folderId, like a workspace points at its section.
- Folder header with chevron collapse, coloured left border, inline rename and
  a right-click/hover actions menu (rename, colour, delete).
- 'Move to folder' submenu on a project's context menu, including
  'New folder...' and 'Remove from folder'.
- Deleting a folder returns its repos to the sidebar root, never deletes them;
  a project pointing at a missing folder falls back to the root.

Named 'folder' rather than 'group' because the UI already calls a section of
workspaces inside one project a 'group'.

Unit tests cover the folder mutations.
Folders could only be created from a project's context menu, so there was no
way to create one before adding a repo. Adds 'New folder' to the PROJECTS
header's add-repository menu, dropping straight into inline rename.
@ghost

ghost commented Jul 27, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 7 individual chapters for you:

Title
1 Define folder schema and types
2 Implement folder utilities and DnD helpers
3 Implement folder mutations and state logic
4 Update data fetching for folders
5 Create folder UI components and context
6 Integrate folders into sidebar layout
7 Add folder actions to menus
Open in Stage

Chapters generated by Stage for commit e1eb2f9 on Jul 30, 2026 7:07pm UTC.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds persisted dashboard folders with lifecycle mutations, folder-aware project placement, context-menu actions, inline renaming, color and collapse controls, and grouped sortable rendering in the authenticated dashboard sidebar.

Changes

Dashboard sidebar folders

Layer / File(s) Summary
Folder persistence and data contracts
apps/desktop/src/renderer/providers/CollectionsProvider/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/...
Sidebar schemas, collections, project types, queries, and fixtures now represent folders and nullable project folder assignments.
Folder state mutations
apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/...
Folder creation, renaming, collapse, color, project movement, deletion cleanup, and reordering are implemented and tested.
Folder action context
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/*
Folder data and project-folder actions are exposed through a sidebar context provider and hook.
Grouped folder sidebar UI
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/*
The sidebar renders folder headers and grouped sortable projects, supports folder menus and inline renaming, and adds folder actions to project and workspace menus.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DashboardSidebar
  participant useDashboardSidebarState
  participant SidebarCollections
  User->>DashboardSidebar: Create or manage folder
  DashboardSidebar->>useDashboardSidebarState: Invoke folder action
  useDashboardSidebarState->>SidebarCollections: Update folder/project rows
  SidebarCollections-->>DashboardSidebar: Provide updated folders and projects
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title uses conventional commit format and clearly summarizes the primary change: grouping v2 sidebar projects into folders.
Description check ✅ Passed The description explains the feature, motivation, implementation, testing, related issues, and scope, but does not use the template headings exactly.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx (1)

161-165: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Trigger label no longer describes the menu.

The dropdown's aria-label/tooltip is still "Add repository" (Lines 124, 134) although it now also creates folders. Consider relabelling to something like "Add" or "Add repository or folder".

🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx`
around lines 161 - 165, Update the dropdown trigger’s aria-label and tooltip
near the workspace header to describe both available actions, replacing “Add
repository” with a label such as “Add” or “Add repository or folder”; keep the
existing menu actions unchanged.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarProjectContextMenu/DashboardSidebarProjectContextMenu.tsx (1)

78-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Same icon for two different actions.

LuFolderPlus is already used by the top-level "New group" item (Line 68); reusing it for "New folder…" makes the two indistinguishable at a glance. Consider LuFolders-style variant or another distinct icon for one of them.

🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarProjectContextMenu/DashboardSidebarProjectContextMenu.tsx`
around lines 78 - 81, Use a distinct icon for the “New folder…” ContextMenuItem
in DashboardSidebarProjectContextMenu instead of reusing LuFolderPlus from the
top-level “New group” action. Update only the icon associated with
onCreateFolderWithProject, selecting an available LuFolders-style or otherwise
visually distinct folder icon.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/DashboardSidebarFolderContext.tsx (1)

38-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist the fallback to a module-level constant.

The fallback object is re-created on every render outside a provider, giving consumers an unstable value identity if they ever memo on it.

♻️ Proposed refactor
+const FALLBACK_VALUE: DashboardSidebarFolderContextValue = {
+	folders: [],
+	moveProjectToFolder: () => {},
+	createFolderForProject: () => {},
+};
+
 export function useDashboardSidebarFolders(): DashboardSidebarFolderContextValue {
-	return (
-		useContext(DashboardSidebarFolderContext) ?? {
-			folders: [],
-			moveProjectToFolder: () => {},
-			createFolderForProject: () => {},
-		}
-	);
+	return useContext(DashboardSidebarFolderContext) ?? FALLBACK_VALUE;
 }
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/DashboardSidebarFolderContext.tsx`
around lines 38 - 46, Hoist the fallback DashboardSidebarFolderContextValue
object used by useDashboardSidebarFolders to a module-level constant, then
return that shared constant when useContext(DashboardSidebarFolderContext) is
nullish. Preserve the existing empty folders array and no-op callbacks while
ensuring the fallback identity remains stable across renders.
apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.ts (1)

24-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate random-color-picking logic.

pickFolderColor() duplicates the inline random-color selection already used in useDashboardSidebarState.ts's createSection (PROJECT_CUSTOM_COLORS[Math.floor(Math.random() * PROJECT_CUSTOM_COLORS.length)].value). Consider extracting a shared pickRandomProjectColor() helper (e.g. co-located with PROJECT_CUSTOM_COLORS in shared/constants/project-colors.ts) so both folders and sections stay in sync if the palette or selection strategy changes.

🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.ts`
around lines 24 - 28, Extract the random project-color selection into a shared
pickRandomProjectColor helper near PROJECT_CUSTOM_COLORS, then update
pickFolderColor and createSection in useDashboardSidebarState to call it instead
of duplicating the selection expression.
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx`:
- Around line 136-155: Clear autoRenameFolderId when the auto-initiated rename
ends: in DashboardSidebar.tsx, pass an onAutoRenameEnd handler to
DashboardSidebarFolderHeader that resets the flag; in
DashboardSidebarFolderHeader.tsx, invoke the callback from both submitRename and
cancelRename when autoRename initiated the edit.
- Around line 352-399: Update the SortableContext items prop to follow the DOM
render order: flatten project IDs from foldersWithProjects in folder/project
sequence, then append IDs from ungroupedProjects. Stop deriving items from
displayedGroups while preserving the existing rendered SortableProjectWrapper
order.

In
`@apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.ts`:
- Around line 31-48: Update createFolderInState to trim the provided name before
insertion and treat a blank trimmed value as absent, falling back to
DEFAULT_FOLDER_NAME. Ensure the inserted folder uses the validated fallback or
trimmed name, matching renameFolderInState’s behavior while preserving the
existing default.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/DashboardSidebarFolderContext.tsx`:
- Around line 38-46: Hoist the fallback DashboardSidebarFolderContextValue
object used by useDashboardSidebarFolders to a module-level constant, then
return that shared constant when useContext(DashboardSidebarFolderContext) is
nullish. Preserve the existing empty folders array and no-op callbacks while
ensuring the fallback identity remains stable across renders.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarProjectContextMenu/DashboardSidebarProjectContextMenu.tsx`:
- Around line 78-81: Use a distinct icon for the “New folder…” ContextMenuItem
in DashboardSidebarProjectContextMenu instead of reusing LuFolderPlus from the
top-level “New group” action. Update only the icon associated with
onCreateFolderWithProject, selecting an available LuFolders-style or otherwise
visually distinct folder icon.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx`:
- Around line 161-165: Update the dropdown trigger’s aria-label and tooltip near
the workspace header to describe both available actions, replacing “Add
repository” with a label such as “Add” or “Add repository or folder”; keep the
existing menu actions unchanged.

In
`@apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.ts`:
- Around line 24-28: Extract the random project-color selection into a shared
pickRandomProjectColor helper near PROJECT_CUSTOM_COLORS, then update
pickFolderColor and createSection in useDashboardSidebarState to call it instead
of duplicating the selection expression.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 988679d6-f1e8-44fd-9614-f38ae8e0002a

📥 Commits

Reviewing files that changed from the base of the PR and between 97e0446 and 4460f0f.

📒 Files selected for processing (18)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/DashboardSidebarFolderContext.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderHeader/DashboardSidebarFolderHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderHeader/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarProjectContextMenu/DashboardSidebarProjectContextMenu.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/testProjectFixtures.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts

- SortableContext items now match the rendered order (folders' projects in
  folder order, skipping collapsed folders, then ungrouped). dnd-kit requires
  the items array to match visual order or project reordering misbehaves.
- Auto-rename no longer latches: the folder header reports when an
  auto-initiated rename is committed or cancelled so the pending id is
  cleared, instead of re-opening the input on every remount.
- createFolderInState trims the name and falls back to the default, mirroring
  renameFolderInState, so a blank override can't fail the schema at insert.
@ashbrener

Copy link
Copy Markdown
Contributor Author

Thanks — all three addressed in 2289a6d74:

  • SortableContext order (Major) — good catch, this was a real bug. The items array was still the pre-folder ordering while the list renders folder-by-folder. Now built to match exactly what's rendered: each folder's projects in folder order (skipped while a folder is collapsed and therefore unmounted), then the ungrouped ones.
  • Auto-rename latch — the header now reports when an auto-initiated rename is committed or cancelled (onAutoRenameEnd), so the pending id is cleared instead of re-opening the input on remount.
  • createFolderInState didn't trim — now trims and falls back to the default name, mirroring renameFolderInState, so a whitespace-only name can't fail the schema's .min(1) at insert. Added two tests covering blank and padded names.

typecheck, Biome and the desktop suite (2304 tests) are green, and I re-checked the auto-rename fix in the running app — no rename input re-opens after a remount.

Folder headers and the ungrouped root list are now drop targets, so a repo
can be dragged into a folder or back out again, alongside the existing
'Move to folder' menu. Dropping on another project adopts that project's
folder, so dragging into a folder's list joins it. Drop targets highlight
while a project hovers them, and the root zone keeps a minimum height during
a drag so an empty root is still targetable.
@ashbrener

Copy link
Copy Markdown
Contributor Author

Added drag & drop in 5915e4193 — this was the part of @de1mat's ask ("repos dragged into them") that the first pass left to the context menu.

Folder headers and the ungrouped root list are now drop targets, so a repo can be dragged into a folder or back out again. Dropping on another project adopts that project's folder, so dragging into a folder's list joins it. Targets highlight while a project hovers them, and the root zone keeps a minimum height during a drag so an empty root is still targetable.

Verified end-to-end in the running app with real mouse input over CDP: dragged a repo from Work onto Personal (counts went 1→0 and 0→1, and the persisted folderId changed), then dragged it back out to the root (both counts 0, folderId cleared). typecheck, Biome and the 2304 desktop tests are green.

The project context menu offered "New group" (workspaces inside the repo)
and "New folder…" (this PR's project folders) two items apart, both with
the same LuFolderPlus icon, so the two levels read as one feature. Name
the level in each label and reserve LuFolders for the project level.
@ashbrener

ashbrener commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Pushed dfa03ff9a. I hit this myself in the app.

A project's right-click menu had "New group" (groups workspaces inside the repo) and "New folder…" (this PR, groups the repos) two items apart, with the same icon. I couldn't tell them apart.

Now each label says which level it works on. "New group" is "New workspace group", and "New folder" in the PROJECTS header menu is "New project folder". LuFolders is only used for project folders now, LuFolderPlus stays on workspace groups.

Text and icons only, nothing else changed. Typecheck and Biome pass. I've updated the description to match.

A folder's projects ran flush into the projects that follow it, so with a
folder holding one or two repos there was no way to see where its contents
ended. Draw a thin rule above the ungrouped list whenever a folder exists
and something sits outside it.
# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/testProjectFixtures.ts
ashbrener and others added 3 commits July 30, 2026 21:06
ParadevAI#1176 asked for a custom icon per group, with a colour as the fallback.
Folders had the colour; this adds the icon. An emoji picker covers the
quick case and "Choose image…" covers a client's logo — a picked file is
re-encoded down to 64px before it goes in the store, since folder rows
share the sidebar's local quota. Both are held in one nullable string:
emoji as-is, an image as a data URL, which is what the renderer's CSP
allows (a file:// path wouldn't render).
…' into ashbrener/sidebar-project-groups

# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderHeader/DashboardSidebarFolderHeader.tsx
@Kitenite

Kitenite commented Aug 1, 2026

Copy link
Copy Markdown
Member

Pushed three updates to this branch:

  1. Merged main (twice — through 4a6c985). The only overlap was CollectionsProvider/collections.ts meeting the new localStorage quota-guard work; resolved cleanly, typecheck and all sidebar tests green.
  2. Minimal restyle of the folder UI (b428b02): removed the coloured left border from the header; identity is now a small colour dot (or the new icon), and the folder colour instead tints a thin rail that runs down the folder's contents — so where a folder ends is visible at a glance, which also let the border-t divider before the ungrouped list (and its showDivider prop) be deleted. The project count now renders only while the folder is collapsed.
  3. Merged @ashbrener's feat(desktop): give sidebar folders an icon (dfb5984). One conflict in the header render block, resolved per the commit's own intent ([feat] Group Projects #1176): icon when set, colour dot as the fallback.

Verified end-to-end in the dev app over CDP after the merge: emoji icon renders on the header and takes precedence over the dot, an icon-less coloured folder falls back to the dot, the tinted rail follows the folder's contents, and drag-in/drag-out re-parenting still works with the new indented wrapper. bun run lint, typecheck, and all folder/sidebar unit tests (including the new icon mutation cases) pass.

One heads-up unrelated to this PR: while re-testing, sidebar folder state vanished across an app restart. That's a pre-existing @tanstack/db local-storage lifecycle issue on main (an insert on a never-synced collection rewrites the whole storage key; a single malformed row blanks hydration) — it affects the existing sidebar collections the same way and is being investigated separately, so it shouldn't block this PR.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 25 files

Architecture diagram
sequenceDiagram
    participant User as User
    participant Sidebar as DashboardSidebar
    participant Dnd as dnd-kit DragContext
    participant FolderContext as DashboardSidebarFolderContext
    participant FolderHeader as DashboardSidebarFolderHeader
    participant ProjectMenu as Project Context Menu
    participant SidebarState as useDashboardSidebarState
    participant Collections as LocalStorage Collections
    participant FolderMutations as folderMutations.ts

    Note over User,Collections: NEW: Folder Creation Flow
    
    User->>Sidebar: Click "New project folder" in PROJECTS header
    Sidebar->>SidebarState: handleNewFolder()
    SidebarState->>FolderMutations: createFolderInState(collections)
    FolderMutations->>Collections: insert folder row
    Collections-->>FolderMutations: folderId (UUID)
    FolderMutations-->>SidebarState: return folderId
    SidebarState-->>Sidebar: set autoRenameFolderId
    
    Sidebar->>FolderHeader: render with autoRename=true
    FolderHeader->>FolderHeader: show RenameInput inline
    User->>FolderHeader: type folder name + Enter
    FolderHeader->>SidebarState: renameFolder(folderId, name)
    SidebarState->>FolderMutations: renameFolderInState(collections, folderId, name)
    FolderMutations->>Collections: update folder name
    Collections-->>FolderMutations: ok
    FolderMutations-->>SidebarState: ok
    SidebarState-->>FolderHeader: onAutoRenameEnd()

    Note over User,Collections: NEW: Move Project to Folder (Context Menu)
    
    User->>ProjectMenu: Right-click project → "Move to folder"
    alt Select existing folder
        User->>ProjectMenu: Click folder name
        ProjectMenu->>FolderContext: onMoveToFolder(folderId)
        FolderContext->>SidebarState: moveProjectToFolder(projectId, folderId)
        SidebarState->>FolderMutations: moveProjectToFolderInState(collections, projectId, folderId)
        FolderMutations->>Collections: update project.folderId
        Collections-->>FolderMutations: ok
    else New folder…
        User->>ProjectMenu: Click "New folder…"
        ProjectMenu->>FolderContext: onCreateFolderWithProject(projectId)
        FolderContext->>SidebarState: createFolder() + moveProjectToFolder()
        SidebarState->>FolderMutations: createFolderInState(collections)
        FolderMutations->>Collections: insert folder
        Collections-->>FolderMutations: folderId
        SidebarState->>FolderMutations: moveProjectToFolderInState(collections, projectId, folderId)
        FolderMutations->>Collections: update project.folderId
        Collections-->>FolderMutations: ok
    else Remove from folder
        User->>ProjectMenu: Click "Remove from folder"
        ProjectMenu->>FolderContext: onMoveToFolder(null)
        FolderContext->>SidebarState: moveProjectToFolder(projectId, null)
        SidebarState->>FolderMutations: moveProjectToFolderInState(collections, projectId, null)
        FolderMutations->>Collections: update project.folderId = null
    end

    Note over User,Collections: NEW: Drag-Drop Project into Folder
    
    User->>Dnd: Drag project onto folder header
    Dnd->>FolderHeader: isOver = true (droppable)
    User->>Dnd: Drop
    Dnd->>Sidebar: handleDragEnd({ active, over })
    Sidebar->>Sidebar: parseFolderDropId(overId) → folderId
    Sidebar->>SidebarState: moveProjectToFolder(activeId, dropFolderId)
    SidebarState->>FolderMutations: moveProjectToFolderInState(collections, projectId, folderId)
    FolderMutations->>Collections: update project.folderId
    Collections-->>FolderMutations: ok

    Note over User,Collections: NEW: Drag-Drop out to Root Zone
    
    User->>Dnd: Drag project onto root drop zone
    Dnd->>RootDropZone: isOver = true
    User->>Dnd: Drop
    Dnd->>Sidebar: handleDragEnd({ active, over })
    Sidebar->>Sidebar: parseFolderDropId(overId) → null
    Sidebar->>SidebarState: moveProjectToFolder(activeId, null)
    SidebarState->>FolderMutations: moveProjectToFolderInState(collections, projectId, null)
    FolderMutations->>Collections: update project.folderId = null

    Note over User,Collections: NEW: Folder Deletion (safe)

    User->>FolderHeader: Click "Delete folder"
    FolderHeader->>SidebarState: deleteFolder(folderId)
    SidebarState->>FolderMutations: deleteFolderInState(collections, folderId)
    FolderMutations->>Collections: find all projects with this folderId
    loop each project in folder
        FolderMutations->>Collections: update project.folderId = null
    end
    FolderMutations->>Collections: delete folder row
    Collections-->>FolderMutations: ok
    Collections-->>Sidebar: re-render with projects at root

    Note over User,Collections: NEW: Folder Collapse / Expand
    
    User->>FolderHeader: Click chevron
    FolderHeader->>SidebarState: toggleFolderCollapsed(folderId)
    SidebarState->>FolderMutations: toggleFolderCollapsedInState(collections, folderId)
    FolderMutations->>Collections: toggle isCollapsed
    Collections-->>FolderMutations: ok
    Collections-->>Sidebar: re-render (hide/show FolderContents)

    Note over User,Collections: NEW: Folder Color / Icon

    User->>FolderHeader: Hover "…" → "Set folder color"
    FolderHeader->>SidebarState: setFolderColor(folderId, color)
    SidebarState->>FolderMutations: setFolderColorInState(collections, folderId, color)
    FolderMutations->>Collections: update folder.color
    Collections-->>FolderMutations: ok
    
    User->>FolderHeader: Hover "…" → "Set folder icon" → pick emoji
    FolderHeader->>SidebarState: setFolderIcon(folderId, emoji)
    SidebarState->>FolderMutations: setFolderIconInState(collections, folderId, emoji)
    FolderMutations->>Collections: update folder.icon
    Collections-->>FolderMutations: ok

    Note over User,Collections: RENDER: Folder Contents Rail

    Sidebar->>FolderContents: render with folder.color
    alt hasCustomColor(color)
        FolderContents->>FolderContents: apply tinted left border (borderColor: `${color}4d`)
    else
        FolderContents->>FolderContents: use default border color
    end
    FolderContents->>User: display projects indented under folder
Loading

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
…view

Seven findings from the review bot on ParadevAI#5981.

The important one: `v2SidebarFolders` was the only local collection not
wrapped in `hardenLocalCollection`. That wrapper is what sets
`startSync`/`gcTime` (without them an insert on a not-yet-hydrated or GC'd
collection rewrites the whole storage key from empty memory), tolerates a
malformed row on read instead of blanking the store, and drops a write on a
full quota instead of throwing into the retry loop that freezes the
renderer. It explains the folder state reported lost across a restart.

Also:
- register `v2-sidebar-folders-*` in the persisted-key registry that landed
  on main today, which CI now enforces
- heal legacy sidebar project rows: written before folders existed, they
  load with `folderId` undefined rather than null, so the menu offered a
  "Remove from folder" that did nothing
- sort folders in JS like the project rows — the query's incremental
  orderBy doesn't re-sort after an insert or a tabOrder renumber
- resolve a drag target's folder against the live folder set, so dropping
  onto a project that renders at root because its folder is gone moves to
  root instead of into the dead folder
- make the folder header one tab stop: the collapse toggle is a real button
  and the actions trigger its sibling, rather than a button nested inside a
  role="button" div
- render the emoji choices as menu items so arrow keys reach them
- extract the colour submenu shared by folders and workspace groups
@ashbrener

ashbrener commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor Author

@Kitenite found the bug behind the folder state lost across a restart — the folders collection wasn't wrapped in hardenLocalCollection like the sibling collections. Fixed in 7c5a7f65b.

That wrapper is what stops an insert on a not-yet-hydrated or GC'd collection rewriting the whole storage key from empty memory, and keeps one malformed entry from blanking the store on hydration.

Same commit registers v2-sidebar-folders-* in the persisted-key registry that landed on main that day, which CI now enforces.

The other six review findings are in there too: folders sort in JS like the project rows (the incremental orderBy doesn't re-sort after an insert), legacy project rows heal folderId from undefined to null so the menu stops offering a "Remove from folder" that does nothing, a drag onto a project that renders at root because its folder is gone now moves it to root, the folder header is one tab stop with a real button instead of a nested control, the emoji picker is reachable by arrow keys, and the colour submenu is shared between folders and workspace groups rather than duplicated.

…uping fixture

Project rows gained a required `color` field in ParadevAI#6249; the fixture predates it
and stopped compiling once main was merged in.
Rebuilds folders on top of the sidebar DnD that ParadevAI#6364 landed on main
instead of carrying a second DndContext beside it.

- DashboardSidebar no longer owns a DndContext, sensors, collision
  detection, drag-end or a DragOverlay. All of that is now
  DashboardSidebarDndProvider's, which ParadevAI#6364 introduced for pin drag and
  drop; folders just pass their two extra inputs into it.
- useSidebarDnd learns folders: project drags may additionally target a
  folder header or the root zone, and its project drag-end re-parents on
  those drops or adopts the target project's folder when reordering.
- Collision detection for project drags goes pointer-first. A project
  section is measured tall at drag start, so closestCenter put its centre
  far from the cursor and the thin strips were near-impossible to hit.
  pointerWithin also ranks an ungrouped project row above the root zone
  that contains it, so dropping there still reorders.
- RootDropZone reads the active drag type from the shared context rather
  than taking an isDragging prop, and now ignores workspace and section
  drags, which never re-parent a project.
Its pointer-first strategy now lives in useSidebarDnd's project branch,
where it also has to coexist with pin and section drags. Nothing imported
it after that move.
@ashbrener
ashbrener force-pushed the sidebar-project-groups branch from e6a360f to d2c7ae6 Compare August 12, 2026 15:12
@ashbrener

Copy link
Copy Markdown
Contributor Author

@Kitenite I've rebuilt this on top of your #6364 rather than beside it. The sidebar no longer has its own DndContext, sensors, drag-end or DragOverlay; all of that is DashboardSidebarDndProvider now. Folders pass two extra inputs (folders, onMoveProjectToFolder) and useSidebarDnd learned that a project drag can also land on a folder header or the root zone.

The one bit worth your eyes: project drags now go pointer-first. A project section is measured tall at drag start, so closestCenter put its centre far from the cursor and the thin targets were near-impossible to hit. pointerWithin also ranks an ungrouped row above the root zone containing it, so dropping there still reorders. Pin and section drags are untouched.

Also dropped my folderAwareCollisionDetection helper — yours covers it now.

@ashbrener

Copy link
Copy Markdown
Contributor Author

For anyone following this one — folders are half of it. #5337 is the other half: each window keeps its own organization, so you can group projects inside an org and spread orgs across windows. I run both together as my daily build and the combination is what makes a lot of repos manageable.

A design question I'd like maintainer input on, because it changes the shape of #5337:

Should a new window inherit the current window's org, or always ask? Today it inherits, and the window-to-org binding persists across a relaunch through a main-process window registry. The alternative is to keep per-window org in the renderer session and treat the registry as a cache. Inheriting is what I want day to day, but it makes "open my other org" two steps instead of one.

Second one, smaller: folder state is per-org, so with per-window org it resolves per window. That seems right to me, but it's worth deciding deliberately rather than by accident.

Background on the demand for both: #4018 and #3114.

- Folder rendering keeps main's new SectionDragSpacer and StarNagCard, and
  moves to the sidebar-sections collapse store.
- selectableWorkspaceIds adopts main's collapsed-section pruning.
- The Projects header is main's shared DashboardSidebarSectionHeader now;
  'New project folder' rides on it as an extra dropdown item.
Main restructured the sidebar again (SidebarCardSlot, cloud section, ports
provider hoisted to the dashboard layout), so the folder rendering is
re-applied on top of its tree rather than the other way round.

The project context menu takes main's plain 'Remove from Sidebar' styling
— the destructive variant was mine and main has since settled on plain —
keeping only the 'Move to folder' submenu from this branch.
@ashbrener

Copy link
Copy Markdown
Contributor Author

Brought current with main (was 185 behind) now that #5337 has landed.

Main restructured the sidebar again in the meantime — SidebarCardSlot, the cloud section, and the ports provider hoisted to the dashboard layout — so I re-applied the folder rendering on top of that tree rather than resolving the other way round. The project context menu now takes main's plain "Remove from Sidebar" styling; the destructive variant was mine and main has since settled on plain, so this keeps only the "Move to folder" submenu from here.

Typecheck and Biome clean. Desktop suite: 2758 pass / 27 fail, and 27 is main's own baseline on this machine, so no new failures. 76 sidebar tests pass.

@Kitenite this is the one you built #6364 on — it's the last piece of the folders work still outstanding, and it's ready whenever you have a window.

Main restructured the sidebar again (SidebarCardSlot, cloud section, ports
provider hoisted to the dashboard layout), so the folder rendering is
re-applied on top of its tree rather than the other way round.

The project context menu takes main's plain 'Remove from Sidebar' styling
— the destructive variant was mine and main has since settled on plain —
keeping only the 'Move to folder' submenu from this branch.
# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
@ashbrener

Copy link
Copy Markdown
Contributor Author

@Kitenite flagging a collision rather than asking for a review: #6278 and this PR rewrite the same nine files, including DashboardSidebar.tsx, useDashboardSidebarState.ts, schema.ts, buildDashboardSidebarProjects.ts and useSidebarDnd.ts. Both change the sidebar state shape and both touch the DnD sensors, so whichever lands second is a rebuild, not a rebase.

They're not the same feature — #6278 nests workspaces inside a project, this nests projects inside a folder — so I don't think either makes the other redundant. It's purely that they occupy the same files.

Two ways to sequence it, and I'm happy either way:

  1. feat(desktop): nestable sidebar workspace groups (stacked on #6274) #6278 first. I rebuild folders on top of the nestable tree. If parentSectionId generalises to projects, folders may collapse into a thin layer over your schema rather than a parallel one, which is probably the better end state.
  2. This first. It's smaller and already current with main; you'd rebase feat(desktop): nestable sidebar workspace groups (stacked on #6274) #6278's tree onto the folder rendering.

I'd lean towards 1 on architecture grounds even though it costs me the rework — one grouping primitive beats two. But #6278 hasn't moved since 9 Aug, so if it's parked, 2 avoids blocking both indefinitely.

Which order do you want? I'll do the rework on my side either way.

@ashbrener

Copy link
Copy Markdown
Contributor Author

Following up because #6936 landed and changed the cost of the answer.

This PR is now conflicting on six files — DashboardSidebar.tsx, DashboardSidebarBulkActions, DashboardSidebarProjectContextMenu, DashboardSidebarProjectRow, SectionActionsMenuItems, DashboardSidebarWorkspacesHeader — and _dashboard/components is in ENFORCED_DIRS now, so the rebase isn't textual: the ten strings the folder UI adds across seventeen components need converting too.

That's fine, and I'll do it. What I don't want to do is do it twice. #6278 rewrites the same files, and if it lands after I've converted them I rebase the whole thing again — so the sequencing question from my earlier comment is now the thing blocking me rather than a nicety.

If #6278 goes first I'll rebuild folders on top of the nestable tree and take the i18n pass in the same go, which is one conversion instead of two and probably a smaller diff, since parentSectionId may generalise to projects. If it's parked, say so and I'll rebase this onto main today.

I did #6064 rather than sit idle — it was the same collision in one file, it's rebased, converted (dashboard.sidebar.projectMenu.moveToOrganization, with the sixteen catalogs filled), and green.

…ck errors remain

Conflicts resolved; outstanding work recorded in the session notes:
ColorMenuItems import dropped from SectionActionsMenuItems, and
PROJECT_COLORS[].name is now a lazy () => string for Lingui.
… names

PROJECT_COLORS[].name became a `() => string` thunk on main so the constants
module stays importable from the Electron main process, which does not run the
Lingui macro transform. Resolve it at the call site, matching ColorSelector.

SectionActionsMenuItems kept main's inline colour list from the merge while its
render already delegated to the shared ColorMenuItems, leaving the list dead and
the import missing. Drop the dead list and wire the import back up.
…ction"

Main uses "group" for workspace grouping and reserves "folder" for a directory
on disk ("Project folder is not on this machine", "Hidden folders"). ParadevAI#5981's
construct — a grouping of projects at the sidebar root — was a third meaning of
those two words in the same context menu.

"Collection" collides with neither, and matches what the provider was already
called (CollectionsProvider/collections.ts). Persisted rows move from folderId
to collectionId and the local collection id from v2_sidebar_folders to
v2_sidebar_collections; no migration, as this has never shipped.

Main's folder vocabulary is untouched: schema.ts now only adds lines.
Clears the last hardcoded JSX text in _dashboard/components, which the
enforced-dirs ratchet gates. Catalog entries are left empty for the translate
step rather than hand-filled.

Two strings changed while being wrapped: the bulk toolbar built "N project(s)"
by concatenating an English plural, which no other locale can express that way,
so it becomes an ICU <Plural>; and the workspaces header said "New project
collection" where the context menu and bulk toolbar both say "New collection".
22 new messages across all 16 locales. Written against each catalog's own
register and plural categories rather than substituted into the existing
"group" strings: cs/pl/ru carry one/few/many/other, and the noun had to decline
correctly in each frame.

Machine-translated and not reviewed by native speakers — the same standing as
the Translate Catalogs workflow's output, which cannot run here because it is
gated on same-repo PRs.
The rename moved the writer to v2-sidebar-collections-* but left the registry
allowlisting v2-sidebar-folders-*, so it named a key nothing writes and omitted
the one that does. The registry test still passed, so CI would not have caught
it.

Registers the new key and retires the old one through DEAD_KEYS, per the
localStorage policy in apps/desktop/AGENTS.md: a profile that ran the branch
before the rename has rows under the old key that nothing reads any more.
…base

# Conflicts:
#	apps/desktop/src/renderer/lib/persisted-keys/persisted-keys.ts
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSection/components/DashboardSidebarSectionContextMenu/components/SectionActionsMenuItems/SectionActionsMenuItems.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/providers/DashboardSidebarSelectionProvider/DashboardSidebarSelectionProvider.tsx
#	packages/i18n/locales/cs/messages.ts
#	packages/i18n/locales/de/messages.ts
#	packages/i18n/locales/en/messages.ts
#	packages/i18n/locales/es/messages.ts
#	packages/i18n/locales/fr/messages.ts
#	packages/i18n/locales/id/messages.ts
#	packages/i18n/locales/it/messages.ts
#	packages/i18n/locales/ja/messages.ts
#	packages/i18n/locales/ko/messages.ts
#	packages/i18n/locales/nl/messages.ts
#	packages/i18n/locales/pl/messages.ts
#	packages/i18n/locales/pt-BR/messages.ts
#	packages/i18n/locales/ru/messages.ts
#	packages/i18n/locales/tr/messages.ts
#	packages/i18n/locales/vi/messages.ts
#	packages/i18n/locales/zh-CN/messages.ts
#	packages/i18n/locales/zh-TW/messages.ts
@ashbrener

Copy link
Copy Markdown
Contributor Author

Brought current with main (was 36 behind) and renamed the feature. This is mergeable again.

"Folder" became "collection". Main now uses two of these words for other things, and this PR was quietly claiming a third meaning for one of them. In main today:

With this PR applied, a project's right-click menu carried "New group", "Hidden folders" and "New folder…" together, meaning three different things. I hit the same problem once before in this PR and papered over it by qualifying the labels; that stopped working once #6990/#7037 landed tag folders.

"Collection" collides with neither, and it's what the provider was already called (CollectionsProvider/collections.ts), so this is closer to a correction than a new coinage. I'm aware #4018 asked for "groups" and @de1mat signed off on "folders" — both of those words were taken by the time this landed, which is the only reason I moved off them.

What changed with it:

  • Persisted rows move from folderId to collectionId, and the store from v2-sidebar-folders-* to v2-sidebar-collections-*. The old key is registered in DEAD_KEYS so existing profiles get swept rather than stranded, per the localStorage policy in apps/desktop/AGENTS.md. Anyone who has been running this branch will need to recreate their collections once — there's no migration, since this has never shipped.
  • The colour submenu now goes through the shared ColorMenuItems for both the collection header and the section menu, instead of main's inline copy. PROJECT_COLORS[].name is a thunk now, so it's resolved at the call site the same way ColorSelector does it.
  • _dashboard/components is in ENFORCED_DIRS, so all the user-facing strings are wrapped: 22 new messages, translated across all 16 locales. Two of them changed while being wrapped — the bulk toolbar was building "N project(s)" by concatenating an English plural, which is now an ICU plural, and the workspaces header said "New project collection" where the other two entry points say "New collection".

The translations are machine-generated and haven't been reviewed by native speakers. That's the same standing as the Translate Catalogs workflow's output, which can't run here because it's gated on same-repo PRs — happy to have any of them reworded.

SortableContext still takes the collection-aware ordering rather than main's flat projectOrder, which is the fix from the ordering bug earlier in this PR.

Verified: desktop typecheck clean, packages/i18n check green, and the sidebar/route suites pass — the 9 failures in routes/_authenticated reproduce identically on a clean main checkout, so they're not from this branch.

CPUtester5465 pushed a commit to CPUtester5465/superset that referenced this pull request Sep 3, 2026
…undary rule

Third pass, after the 2026-09-03 decisions. Waiting: ParadevAI#7018 (GitLab review
pane, branch already verified against nested-group remotes) and ParadevAI#5981
(sidebar collections). Building: the terminals-list contribution branch,
plus a dormant fork/patched option for cherry-picking stalled upstream
PRs into a canary build. ClickUp/Shortcut/Telegram never enter the fork:
the integration framework is cloud-side and undeployable from a fork, so
they live at the agent layer (MCPs) and tg-gateway instead. Sibli work
repos stay read-only -- no .superset configs committed there.
# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarBulkActions/DashboardSidebarBulkActions.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarProjectContextMenu/DashboardSidebarProjectContextMenu.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSection/components/DashboardSidebarSectionContextMenu/components/SectionActionsMenuItems/SectionActionsMenuItems.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/providers/DashboardSidebarDndProvider/DashboardSidebarDndProvider.tsx
#	apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
#	packages/i18n/locales/cs/messages.po
#	packages/i18n/locales/cs/messages.ts
#	packages/i18n/locales/de/messages.po
#	packages/i18n/locales/de/messages.ts
#	packages/i18n/locales/en/messages.po
#	packages/i18n/locales/en/messages.ts
#	packages/i18n/locales/es/messages.po
#	packages/i18n/locales/es/messages.ts
#	packages/i18n/locales/fr/messages.po
#	packages/i18n/locales/fr/messages.ts
#	packages/i18n/locales/id/messages.po
#	packages/i18n/locales/id/messages.ts
#	packages/i18n/locales/it/messages.po
#	packages/i18n/locales/it/messages.ts
#	packages/i18n/locales/ja/messages.po
#	packages/i18n/locales/ja/messages.ts
#	packages/i18n/locales/ko/messages.po
#	packages/i18n/locales/ko/messages.ts
#	packages/i18n/locales/nl/messages.po
#	packages/i18n/locales/nl/messages.ts
#	packages/i18n/locales/pl/messages.po
#	packages/i18n/locales/pl/messages.ts
#	packages/i18n/locales/pt-BR/messages.po
#	packages/i18n/locales/pt-BR/messages.ts
#	packages/i18n/locales/ru/messages.po
#	packages/i18n/locales/ru/messages.ts
#	packages/i18n/locales/tr/messages.po
#	packages/i18n/locales/tr/messages.ts
#	packages/i18n/locales/vi/messages.po
#	packages/i18n/locales/vi/messages.ts
#	packages/i18n/locales/zh-CN/messages.po
#	packages/i18n/locales/zh-CN/messages.ts
#	packages/i18n/locales/zh-TW/messages.po
#	packages/i18n/locales/zh-TW/messages.ts

This branch has not been deployed

No deployments
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.

2 participants