Repository navigation
Conversation
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.
|
Ready to review this PR? Stage has broken it down into 7 individual chapters for you: Chapters generated by Stage for commit e1eb2f9 on Jul 30, 2026 7:07pm UTC. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. ChangesDashboard sidebar folders
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 valueTrigger 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 valueSame icon for two different actions.
LuFolderPlusis already used by the top-level "New group" item (Line 68); reusing it for "New folder…" makes the two indistinguishable at a glance. ConsiderLuFolders-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 valueHoist 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 winDuplicate random-color-picking logic.
pickFolderColor()duplicates the inline random-color selection already used inuseDashboardSidebarState.ts'screateSection(PROJECT_CUSTOM_COLORS[Math.floor(Math.random() * PROJECT_CUSTOM_COLORS.length)].value). Consider extracting a sharedpickRandomProjectColor()helper (e.g. co-located withPROJECT_CUSTOM_COLORSinshared/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
📒 Files selected for processing (18)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/DashboardSidebarFolderContext.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderContext/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderHeader/DashboardSidebarFolderHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderHeader/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarProjectContextMenu/DashboardSidebarProjectContextMenu.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.test.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/testProjectFixtures.tsapps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.test.tsapps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/folderMutations.tsapps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.tsapps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.tsapps/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.
|
Thanks — all three addressed in
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.
|
Added drag & drop in 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 |
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.
|
Pushed 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". 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
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).
…d content rail, collapsed-only count
…' into ashbrener/sidebar-project-groups # Conflicts: # apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarFolderHeader/DashboardSidebarFolderHeader.tsx
|
Pushed three updates to this branch:
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. One heads-up unrelated to this PR: while re-testing, sidebar folder state vanished across an app restart. That's a pre-existing |
There was a problem hiding this comment.
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
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
|
@Kitenite found the bug behind the folder state lost across a restart — the folders collection wasn't wrapped in 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 The other six review findings are in there too: folders sort in JS like the project rows (the incremental |
…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.
e6a360f to
d2c7ae6
Compare
|
@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 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. Also dropped my |
|
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. |
- 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.
|
Brought current with Main restructured the sidebar again in the meantime — Typecheck and Biome clean. Desktop suite: 2758 pass / 27 fail, and 27 is @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
|
@Kitenite flagging a collision rather than asking for a review: #6278 and this PR rewrite the same nine files, including 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:
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. |
|
Following up because #6936 landed and changed the cost of the answer. This PR is now conflicting on six files — 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 I did #6064 rather than sit idle — it was the same collision in one file, it's rebased, converted ( |
…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
|
Brought current with "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
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 ( What changed with it:
The translations are machine-generated and haven't been reviewed by native speakers. That's the same standing as the
Verified: desktop typecheck clean, |
…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
Description
Adds a folder level above projects in the v2 dashboard sidebar, so repos can be grouped by context — e.g.
WorkvsPersonal— 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
v2SidebarFolders(name,color,isCollapsed,tabOrder), mirroring the existingv2SidebarSectionsone level down; a project points at its folder via a nullablefolderId, exactly like a workspace points at its section viasidebarState.sectionId.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
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):
Work; persisted tov2-sidebar-folders-<org>.Personal— both render with their own colour and project count.Work: true,Personal: false).Workshows it nested underneath.bun run typecheck, Biome, andbun test(2302 desktop tests) are green.Screenshots (if applicable)
Sidebar with
Work(purple, containingacme-web) andPersonal(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:
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.
v2SidebarCollectionsand a nullablecollectionIdon projects; registersv2-sidebar-collections-*, retires the pre-renamev2-sidebar-folders-*via DEAD_KEYS, hardens the collection, re-sorts in JS, and heals legacycollectionId: undefinedtonull. No host/API changes.Written for commit f6d3e19. Summary will update on new commits.
Summary by CodeRabbit