Skip to content

feat(desktop): add Create/Import project to v2 workspace picker#3846

Merged
Kitenite merged 7 commits into
mainfrom
update-workspace-dropdown
Apr 30, 2026
Merged

feat(desktop): add Create/Import project to v2 workspace picker#3846
Kitenite merged 7 commits into
mainfrom
update-workspace-dropdown

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 28, 2026

  • 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.

Description

Related Issues

Type of Change

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

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

    • Added “Create new project” and “Import project” to the v2 ProjectPickerPill; pinned below the scrolling list (max height 280px) and wired to existing flows.
    • Moved the v2 sidebar Add Repository button next to New Workspace; renamed to “Create new project” / “Import project”.
    • Picker import flow now toasts “Project imported and selected.”
  • Refactors

    • openNewProject() now returns a promise with single-in-flight semantics; folderImport.start() returns a promise with the setup result.
    • Introduced useFinalizeProjectSetup to update the sidebar and invalidate hostProjectListQueryKey so needsSetup re-evaluates immediately.
    • Extracted useHostProjectIds (exports hostProjectListQueryKey) and now logs host fetch errors.
    • New Workspace modal waits for org context before initializing and avoids reselecting after the initial pick.
    • Standardized on a shared ProjectSetupResult and 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

    • Modals and project pickers: "Create new project" and "Import project" now return the created/imported project to callers; modal open returns a Promise-resolvable result.
  • Improvements

    • Consolidated post‑creation/import finalization so sidebar updates and toasts are applied consistently.
    • Centralized host-project ID fetching for more reliable project lists in pickers and dialogs.
    • Sidebar header and import dropdown layout refined.
  • Bug Fixes

    • Prevents accidental reselection when new projects appear after a modal opens.

- 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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

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 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

Cohort / File(s) Summary
React Query exports
apps/desktop/src/renderer/react-query/projects/index.ts
Re-exports ProjectSetupResult, useFinalizeProjectSetup, hostProjectListQueryKey, and useHostProjectIds.
Finalize setup hook
apps/desktop/src/renderer/react-query/projects/useFinalizeProjectSetup/useFinalizeProjectSetup.ts, apps/desktop/src/renderer/react-query/projects/useFinalizeProjectSetup/index.ts
Adds ProjectSetupResult type and useFinalizeProjectSetup() hook that ensures workspace/project in sidebar and invalidates hostProjectListQueryKey(hostUrl).
Host project IDs hook
apps/desktop/src/renderer/react-query/projects/useHostProjectIds/useHostProjectIds.ts, apps/desktop/src/renderer/react-query/projects/useHostProjectIds/index.ts
Adds hostProjectListQueryKey(hostUrl) and useHostProjectIds(hostUrl) returning `Set
Modal store & API
apps/desktop/src/renderer/stores/add-repository-modal.ts
Converts openNewProject to `() => Promise<NewProjectResult
New Project modal wiring
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/components/NewProjectModal/NewProjectModal.tsx, .../AddRepositoryModals/AddRepositoryModals.tsx
Uses useFinalizeProjectSetup() for post-creation handling and wires modal success to resolve modal promise via useResolveNewProjectModal.
Folder-first import
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts
start() now returns `Promise<ProjectSetupResult
Sidebar/header & project picker
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx, apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/.../useSelectedHostProjectIds.ts, .../ProjectPickerPill/ProjectPickerPill.tsx, .../DashboardNewWorkspaceModalContent.tsx
Moves import success toast to caller, updates dropdown labels/layout, replaces local project-id query with useHostProjectIds, adds create/import actions in project picker, and guards modal default-selection initialization.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through hooks and promises bright,

Finalizers nudged sidebars into sight,
Queries wake, IDs gathered in a set,
Modals promise back the project we met,
Hop-hop hooray — the dashboard's all right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main feature: adding Create/Import project functionality to the v2 workspace picker in the desktop app.
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.
Description check ✅ Passed The pull request description provides comprehensive details of changes, clearly structured with objectives and implementation details that align well with the template structure.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch update-workspace-dropdown

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

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR wires "Create new project" and "Import project" actions into the v2 workspace picker (ProjectPickerPill) and moves the Add Repository dropdown next to New Workspace in the expanded sidebar. The core refactor converts openNewProject() to return a promise (via a module-level resolver pattern) and useFolderFirstImport.start() to return Promise<ProjectSetupResult | null>, letting callers await the result in normal React flow instead of relying on callbacks crossing the Zustand store. Post-setup side effects are consolidated into useFinalizeProjectSetup, useHostProjectIds is extracted with an exported hostProjectListQueryKey for targeted invalidation, and hasInitializedSelectionRef prevents the auto-selection effect from overriding a freshly-created project when the query re-fetches.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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)
Loading
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.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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."

Suggested change
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.

Comment thread apps/desktop/src/renderer/stores/add-repository-modal.ts
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 | 🟠 Major

Avoid finalizing initialization before org context is ready.

At Line 151, initialization is marked complete even if projects were fetched before activeOrganizationId is 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

📥 Commits

Reviewing files that changed from the base of the PR and between e76dd25 and 28aaa04.

📒 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.
@Kitenite Kitenite merged commit 735e001 into main Apr 30, 2026
6 of 7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

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.

1 participant