feat(desktop): add Create/Import project to v2 workspace picker#3846
Conversation
- Add "Create new project" and "Import project" entries to the v2 ProjectPickerPill in the New Workspace modal; wired to the same flows used by the sidebar Add Repository button. - Move v2 sidebar Add Repository button next to New Workspace; rename dropdown items to "Create new project" / "Import project". - Refactor: openNewProject() and folderImport.start() now return promises (resolved by the modal/flow) instead of callbacks crossing a zustand store; consumers await and react in normal React flow. - Extract useFinalizeProjectSetup() and useHostProjectIds (with exported queryKey) into renderer/react-query/projects/. After a project is created/imported the cached host project list is invalidated so needsSetup re-evaluates correctly.
|
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 two new React Query hooks (finalize setup and host project IDs), converts New Project modal to an async promise-based API, and updates import/create flows and several UI components to delegate post-creation sidebar updates to the new hooks. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Modal as NewProjectModal
participant Store as AddRepoModalStore
participant Finalizer as useFinalizeProjectSetup
participant Sidebar as DashboardSidebarState
participant Query as QueryClient
User->>Modal: submit create/import
Modal->>Store: open (await Promise)
Modal->>Finalizer: finalizeSetup(hostUrl, result)
Finalizer->>Sidebar: ensureWorkspaceInSidebar / ensureProjectInSidebar
Sidebar-->>Finalizer: confirmed
Finalizer->>Query: invalidateQueries(hostProjectListQueryKey(hostUrl))
Query-->>Finalizer: acknowledged
Finalizer-->>Modal: done
Modal->>Store: resolveNewProject(result)
Store-->>User: promise resolves with result
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
Greptile SummaryThis PR wires "Create new project" and "Import project" actions into the v2 workspace picker ( Confidence Score: 5/5Safe to merge — all remaining findings are P2 style/UX suggestions that do not affect correctness. The core promise-based modal pattern is well-implemented with proper cleanup (cancel-previous on re-open, resolve-on-close). The hasInitializedSelectionRef fix correctly handles the cache-invalidation race. The only issues are a misleading success toast copy and a minor undocumented behaviour of the pendingResolve singleton — neither blocks functionality. ProjectPickerPill.tsx — success toast copy for import should be updated to reflect that the project is already selected, not sidebar-only.
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/stores/add-repository-modal.ts | Converts openNewProject() to return a promise via a module-level pendingResolve; adds resolveNewProject action. Pattern is correct for global modal flow but concurrent invocations silently cancel earlier callers. |
| apps/desktop/src/renderer/react-query/projects/useFinalizeProjectSetup/useFinalizeProjectSetup.ts | New hook that consolidates post-project-setup side effects (sidebar state + query invalidation). Clean extraction. |
| apps/desktop/src/renderer/react-query/projects/useHostProjectIds/useHostProjectIds.ts | Extracts useHostProjectIds from useSelectedHostProjectIds and exports hostProjectListQueryKey for targeted invalidation — correct and well-typed. |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/ProjectPickerPill/ProjectPickerPill.tsx | Adds Create/Import project entries to picker; logic is correct but the import success toast message is misleading (says "open from the sidebar" while the project is auto-selected). |
| apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx | Adds hasInitializedSelectionRef to prevent the auto-selection effect from overriding a freshly-created project selection when the cached query re-fetches. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts | Converts start() to return Promise<ProjectSetupResult |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx | Moves Add Repository dropdown next to New Workspace in the v2 expanded view; removes the icon-only button from the v1 collapsed view. handleImportFolder correctly awaits start() and shows toast only on success. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/AddRepositoryModals.tsx | onSuccess now forwards result to resolveNewProject, completing the promise chain for awaiting callers. |
Sequence Diagram
sequenceDiagram
participant PP as ProjectPickerPill
participant Store as add-repository-modal store
participant NPM as NewProjectModal
participant FPI as useFolderFirstImport
participant FPS as useFinalizeProjectSetup
Note over PP: User selects "Create new project"
PP->>Store: openNewProject() → Promise
Store-->>NPM: active = "new-project" (modal opens)
NPM->>FPS: finalizeSetup(hostUrl, result)
FPS->>FPS: ensureProjectInSidebar / ensureWorkspaceInSidebar
FPS->>FPS: invalidateQueries(hostProjectListQueryKey)
NPM->>Store: resolveNewProject({ projectId })
Store-->>PP: Promise resolves with { projectId }
PP->>PP: onSelectProject(projectId)
Note over PP: User selects "Import project"
PP->>FPI: folderImport.start() → Promise
FPI->>FPI: selectDirectory, project.setup / project.create
FPI->>FPS: finalizeSetup(hostUrl, result)
FPS->>FPS: ensureProjectInSidebar / invalidateQueries
FPI-->>PP: Promise resolves with ProjectSetupResult
PP->>PP: onSelectProject(result.projectId)
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/ProjectPickerPill/ProjectPickerPill.tsx
Line: 62
Comment:
**Misleading success toast after import**
After a successful import inside the workspace picker, the toast reads "open it from the sidebar" — but `onSelectProject` immediately selects the project in the form, so the user doesn't need to go to the sidebar at all. This copy is straight from the sidebar's own handler where navigating to the sidebar is the only available next step, but here the context is different. Consider a message like "Project imported." or "Project ready — it has been selected."
```suggestion
toast.success("Project imported and selected.");
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/desktop/src/renderer/stores/add-repository-modal.ts
Line: 26-32
Comment:
**Concurrent `openNewProject` calls silently cancel the first caller**
`pendingResolve?.(null)` at the top of `openNewProject` is the right guard, but its effect is that if two call sites (e.g. the sidebar dropdown and the picker, opened in quick succession) ever trigger this concurrently, the first caller's `await openNewProject()` resolves to `null` with no indication of why. In practice today there is only one global `NewProjectModal`, so this is unlikely to bite, but worth a JSDoc note so the next person isn't surprised.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat(desktop): add Create/Import project..." | Re-trigger Greptile
| setOpen(false); | ||
| const result = await folderImport.start(); | ||
| if (result) { | ||
| toast.success("Project ready — open it from the sidebar."); |
There was a problem hiding this comment.
Misleading success toast after import
After a successful import inside the workspace picker, the toast reads "open it from the sidebar" — but onSelectProject immediately selects the project in the form, so the user doesn't need to go to the sidebar at all. This copy is straight from the sidebar's own handler where navigating to the sidebar is the only available next step, but here the context is different. Consider a message like "Project imported." or "Project ready — it has been selected."
| toast.success("Project ready — open it from the sidebar."); | |
| toast.success("Project imported and selected."); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/ProjectPickerPill/ProjectPickerPill.tsx
Line: 62
Comment:
**Misleading success toast after import**
After a successful import inside the workspace picker, the toast reads "open it from the sidebar" — but `onSelectProject` immediately selects the project in the form, so the user doesn't need to go to the sidebar at all. This copy is straight from the sidebar's own handler where navigating to the sidebar is the only available next step, but here the context is different. Consider a message like "Project imported." or "Project ready — it has been selected."
```suggestion
toast.success("Project imported and selected.");
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
1 issue found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/react-query/projects/useHostProjectIds/useHostProjectIds.ts">
<violation number="1" location="apps/desktop/src/renderer/react-query/projects/useHostProjectIds/useHostProjectIds.ts:21">
P2: Avoid an empty `catch` here; swallowing all query errors with no logging hides failures and makes this hook hard to debug.
(Based on your team's feedback about handling async errors explicitly and avoiding silent failure paths.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- Picker import toast: "Project imported and selected." (no longer tells user to open from sidebar — project is already selected here). - useHostProjectIds: log host fetch errors instead of swallowing. - openNewProject: JSDoc the single-in-flight semantics. - Drop redundant JSDoc / dead type re-export from earlier refactor.
Move the action group out of CommandList so it stays visible when the project list overflows, matching v1 behavior. Tightened max-height to 280px to leave room for the footer.
…down # Conflicts: # apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx (1)
132-151:⚠️ Potential issue | 🟠 MajorAvoid finalizing initialization before org context is ready.
At Line 151, initialization is marked complete even if projects were fetched before
activeOrganizationIdis available (session still loading). In that case, the guard at Line 135 can block the later default auto-pick when real projects arrive.💡 Suggested fix
- const areProjectsReady = v2Projects !== undefined; + const isOrganizationReady = + env.SKIP_ENV_VALIDATION || activeOrganizationId !== null; + const areProjectsReady = isOrganizationReady && v2Projects !== undefined;- if (!areProjectsReady) return; + if (!areProjectsReady) return; // Only auto-pick a default once... if (hasInitializedSelectionRef.current) return;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx` around lines 132 - 151, The initialization flag is being set regardless of whether org context is ready, which can prevent a later correct auto-pick; update the logic around hasInitializedSelectionRef, recentProjects, draft, and updateDraft so you only finalize initialization after activeOrganizationId (or session loaded) is available — e.g., skip setting hasInitializedSelectionRef.current = true and skip returning early when activeOrganizationId is not yet defined, and ensure the default selection/persistedProjectId resolution runs once the real activeOrganizationId and recentProjects are present; reference hasInitializedSelectionRef, recentProjects, draft.selectedProjectId, updateDraft, and useV2WorkspaceCreateDefaultsStore.getState() when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx`:
- Around line 132-151: The initialization flag is being set regardless of
whether org context is ready, which can prevent a later correct auto-pick;
update the logic around hasInitializedSelectionRef, recentProjects, draft, and
updateDraft so you only finalize initialization after activeOrganizationId (or
session loaded) is available — e.g., skip setting
hasInitializedSelectionRef.current = true and skip returning early when
activeOrganizationId is not yet defined, and ensure the default
selection/persistedProjectId resolution runs once the real activeOrganizationId
and recentProjects are present; reference hasInitializedSelectionRef,
recentProjects, draft.selectedProjectId, updateDraft, and
useV2WorkspaceCreateDefaultsStore.getState() when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ef8c98a6-a20d-4a71-836d-c038f64dce3f
📒 Files selected for processing (1)
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx
If session is still loading, activeOrganizationId is null and the v2Projects query filters to []. Initializing the selection in that state would lock in null and skip the real project list once the session resolves. Wait until activeOrganizationId is non-null before marking initialized.
…upResult - useFolderFirstImport: inline onError?.(message) — the wrapper added a hop without buying memoization (onError was already its only dep). - NewProjectModal: use ProjectSetupResult from react-query/projects instead of an inline result shape, avoiding type drift.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Summary by cubic
Added “Create new project” and “Import project” to the v2 workspace picker and aligned the sidebar menu. Actions stay visible under the list, new/imported projects auto-select, setup status refreshes instantly, and the modal avoids init glitches while org context loads.
New Features
ProjectPickerPill; pinned below the scrolling list (max height 280px) and wired to existing flows.Refactors
openNewProject()now returns a promise with single-in-flight semantics;folderImport.start()returns a promise with the setup result.useFinalizeProjectSetupto update the sidebar and invalidatehostProjectListQueryKeysoneedsSetupre-evaluates immediately.useHostProjectIds(exportshostProjectListQueryKey) and now logs host fetch errors.ProjectSetupResultand removed an extra error wrapper in the import hook.Written for commit 93aee61. Summary will update on new commits. Review in cubic
Summary by CodeRabbit
New Features
Improvements
Bug Fixes