feat(desktop): infer project name from folder on import#3605
Conversation
…mport When a folder import has no matching cloud project, infer the project name from the folder basename and create the project directly instead of opening a modal asking the user to type a name. Mirrors v1's upsertProject behavior. Users can still rename after import. Removes FolderFirstImportModal and its no-match state machine.
📝 WalkthroughWalkthroughThis pull request refactors the folder import workflow by removing the modal UI component that collected user input for project naming, simplifying the Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 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 removes the modal prompt that appeared whenever a dragged/selected folder had no matching cloud project. Instead, the folder's basename is automatically inferred as the project name (via the new Key changes:
Confidence Score: 5/5Safe to merge — the simplification is clean and all error paths are correctly handled. The logic is straightforward and correct for all realistic inputs. The two flagged items (fallback in No files require special attention; the core change in
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts | Core logic rewrite: removes the no-match state machine and modal, instead infers project name from the folder basename via inferProjectName and calls project.create directly. Logic is correct; minor fallback edge-case in inferProjectName when path is all separators. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/AddRepositoryModals.tsx | Simplified: removes FolderFirstImportModal rendering; the startRef pattern correctly avoids stale-closure issues. The outer .catch on the effect is dead code since start() never rejects. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/components/FolderFirstImportModal/FolderFirstImportModal.tsx | Deleted — no longer needed since the naming modal is replaced by automatic basename inference. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/index.ts | Simplified barrel export: only useFolderFirstImport and UseFolderFirstImportResult are now exported (the old state-machine types are gone). |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User triggers folder import] --> B[selectDirectory dialog]
B --> C{Canceled or no path?}
C -- Yes --> Z[Return silently]
C -- No --> D[client.project.findByPath]
D --> E{Error?}
E -- Yes --> F[reportError]
E -- No --> G{candidates.length}
G -- "> 1" --> H[reportError: Multiple matches]
G -- "== 1" --> I[client.project.setup.mutate]
G -- "== 0" --> J[inferProjectName from basename]
J --> K[client.project.create.mutate]
I --> L{Error?}
K --> L
L -- Yes --> F
L -- No --> M[reportSuccess → ensureProjectInSidebar + onSuccess toast]
Comments Outside Diff (1)
-
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/AddRepositoryModals.tsx, line 34-39 (link)Outer
.catchis effectively dead codestart()is fully self-contained — every error branch inside it callsreportError(...)and returns before re-throwing, sostart()never rejects. The.catchhere will never fire. This doesn't cause a bug today, but if a future refactor makesstart()actually throw, this catch would silently duplicate the error toast (both the inneronErrorcallback and this outer handler would run).Consider either removing it (relying entirely on the
onErrorcallback) or documenting that it acts as a safety net:Prompt To Fix With AI
This is a comment left during a code review. Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/AddRepositoryModals.tsx Line: 34-39 Comment: **Outer `.catch` is effectively dead code** `start()` is fully self-contained — every error branch inside it calls `reportError(...)` and returns before re-throwing, so `start()` never rejects. The `.catch` here will never fire. This doesn't cause a bug today, but if a future refactor makes `start()` actually throw, this catch would silently duplicate the error toast (both the inner `onError` callback and this outer handler would run). Consider either removing it (relying entirely on the `onError` callback) or documenting that it acts as a safety net: How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts
Line: 11-16
Comment:
**`inferProjectName` fallback leaks trailing separators**
When `repoPath` consists entirely of separators (e.g. `"/"` or `"\\"`) the regex strips them all, leaving `trimmed = ""`. `lastSep` is then `-1` so `name = trimmed = ""`, and the `name || repoPath` fallback returns the original, unstripped path — meaning a project could be created named `"/"`.
Adding `trimmed` as an intermediate fallback avoids this:
```suggestion
function inferProjectName(repoPath: string): string {
const trimmed = repoPath.replace(/[\\/]+$/, "");
const lastSep = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
const name = lastSep < 0 ? trimmed : trimmed.slice(lastSep + 1);
return name || trimmed || repoPath;
}
```
In practice the OS dialog won't let the user pick a raw root directory, so this is a very edge-case concern, but the fix is trivial.
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/routes/_authenticated/_dashboard/components/AddRepositoryModals/AddRepositoryModals.tsx
Line: 34-39
Comment:
**Outer `.catch` is effectively dead code**
`start()` is fully self-contained — every error branch inside it calls `reportError(...)` and returns before re-throwing, so `start()` never rejects. The `.catch` here will never fire. This doesn't cause a bug today, but if a future refactor makes `start()` actually throw, this catch would silently duplicate the error toast (both the inner `onError` callback and this outer handler would run).
Consider either removing it (relying entirely on the `onError` callback) or documenting that it acts as a safety net:
```suggestion
useEffect(() => {
if (folderImportTrigger === 0) return;
// start() handles all errors internally via onError; this catch is a
// safety net for unexpected throws only.
startRef.current().catch((err) => {
toast.error(
`Import failed: ${err instanceof Error ? err.message : String(err)}`,
);
});
}, [folderImportTrigger]);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat(desktop): infer project name from f..." | Re-trigger Greptile
| function inferProjectName(repoPath: string): string { | ||
| const trimmed = repoPath.replace(/[\\/]+$/, ""); | ||
| const lastSep = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")); | ||
| const name = lastSep < 0 ? trimmed : trimmed.slice(lastSep + 1); | ||
| return name || repoPath; | ||
| } |
There was a problem hiding this comment.
inferProjectName fallback leaks trailing separators
When repoPath consists entirely of separators (e.g. "/" or "\\") the regex strips them all, leaving trimmed = "". lastSep is then -1 so name = trimmed = "", and the name || repoPath fallback returns the original, unstripped path — meaning a project could be created named "/".
Adding trimmed as an intermediate fallback avoids this:
| function inferProjectName(repoPath: string): string { | |
| const trimmed = repoPath.replace(/[\\/]+$/, ""); | |
| const lastSep = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")); | |
| const name = lastSep < 0 ? trimmed : trimmed.slice(lastSep + 1); | |
| return name || repoPath; | |
| } | |
| function inferProjectName(repoPath: string): string { | |
| const trimmed = repoPath.replace(/[\\/]+$/, ""); | |
| const lastSep = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")); | |
| const name = lastSep < 0 ? trimmed : trimmed.slice(lastSep + 1); | |
| return name || trimmed || repoPath; | |
| } |
In practice the OS dialog won't let the user pick a raw root directory, so this is a very edge-case concern, but the fix is trivial.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts
Line: 11-16
Comment:
**`inferProjectName` fallback leaks trailing separators**
When `repoPath` consists entirely of separators (e.g. `"/"` or `"\\"`) the regex strips them all, leaving `trimmed = ""`. `lastSep` is then `-1` so `name = trimmed = ""`, and the `name || repoPath` fallback returns the original, unstripped path — meaning a project could be created named `"/"`.
Adding `trimmed` as an intermediate fallback avoids this:
```suggestion
function inferProjectName(repoPath: string): string {
const trimmed = repoPath.replace(/[\\/]+$/, "");
const lastSep = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
const name = lastSep < 0 ? trimmed : trimmed.slice(lastSep + 1);
return name || trimmed || repoPath;
}
```
In practice the OS dialog won't let the user pick a raw root directory, so this is a very edge-case concern, but the fix is trivial.
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 5 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/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts:12">
P2: Manual path parsing for inferred project names is fragile; root paths resolve to invalid/awkward names (e.g. `C:` or `/`). Use a cross-platform basename utility with an explicit root fallback.
(Based on your team's feedback about using cross-platform path utilities instead of manual parsing.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Promote the inline inferProjectName in useFolderFirstImport and the duplicate getBaseName in FilesView/utils into a shared helper at renderer/lib/pathBasename. Fixes the trailing-separator edge case in the old FilesView impl (/foo/ now returns "foo" instead of ""). Add 27 unit tests covering posix, Windows, UNC, trailing/consecutive separators, dotfiles, unicode, emoji, and degenerate inputs.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts`:
- Around line 82-84: Guard against blank or whitespace-only inferred names
returned by getBaseName(repoPath) before calling client.project.create.mutate in
useFolderFirstImport: call getBaseName(repoPath), trim it, and if the result is
empty replace it with a safe fallback name (e.g., "New Project" or a sanitized
repoPath segment) or abort with a user-visible validation error; then pass the
validated/fallback name to client.project.create.mutate instead of the raw
getBaseName result.
🪄 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
Run ID: 7402f734-d078-4d1d-8b5c-5714e247b4c1
📒 Files selected for processing (5)
apps/desktop/src/renderer/lib/pathBasename/index.tsapps/desktop/src/renderer/lib/pathBasename/pathBasename.test.tsapps/desktop/src/renderer/lib/pathBasename/pathBasename.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/utils/new-item-paths.ts
✅ Files skipped from review due to trivial changes (3)
- apps/desktop/src/renderer/lib/pathBasename/pathBasename.ts
- apps/desktop/src/renderer/lib/pathBasename/index.ts
- apps/desktop/src/renderer/lib/pathBasename/pathBasename.test.ts
| const result = await client.project.create.mutate({ | ||
| name, | ||
| name: getBaseName(repoPath), | ||
| mode: { kind: "importLocal", repoPath }, |
There was a problem hiding this comment.
Guard against blank inferred project names before creating.
getBaseName(repoPath) can produce an unusable project name for degenerate selections such as a filesystem root, or for folders whose basename is whitespace. Since the modal validation path is gone, add a local guard before project.create.
Proposed fix
- } else {
+ } else {
+ const inferredName = getBaseName(repoPath).trim();
+ if (!inferredName) {
+ reportError("Could not infer a project name from the selected folder");
+ return;
+ }
+
const result = await client.project.create.mutate({
- name: getBaseName(repoPath),
+ name: inferredName,
mode: { kind: "importLocal", repoPath },
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts`
around lines 82 - 84, Guard against blank or whitespace-only inferred names
returned by getBaseName(repoPath) before calling client.project.create.mutate in
useFolderFirstImport: call getBaseName(repoPath), trim it, and if the result is
empty replace it with a safe fallback name (e.g., "New Project" or a sanitized
repoPath segment) or abort with a user-visible validation error; then pass the
validated/fallback name to client.project.create.mutate instead of the raw
getBaseName result.
Main added a FolderFirstImportModal reference in DashboardSidebarHeader after this branch removed the modal. Remove the stale import and the two renders — the hook now triggers import directly via start().
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Add v2 project setup section (#3566, #3605, #3606, #3592, #3626, #3632), scheduled agent runs (#3576), Opus 4.7 (#3579), v1 review comments in pane (#3596), configurable v2 link-click (#3600), Copy Branch Name (#3635), safer terminal preset defaults (#3546), and /pricing page (#3639). Expand bug fixes with v2 git correctness, cross-fork PR misattribution, terminal paste/Unicode/Shift+Enter, and security bumps.
…-27) (#3792) * docs: generate weekly changelog 2026-04-27 * docs: reframe weekly changelog around v2 public beta Lead with v2 public beta + Settings → Experimental enable, restructure around the v1→v2 migration story, sidebar overhaul, cross-workspace terminals, and v2 chat. Pull in ~30 v2 PRs the bot missed and demote non-v2 items (Hosts page, marketing menu) to a brief "Also this week". * docs: pull in missed v2 features and bug fixes Add v2 project setup section (#3566, #3605, #3606, #3592, #3626, #3632), scheduled agent runs (#3576), Opus 4.7 (#3579), v1 review comments in pane (#3596), configurable v2 link-click (#3600), Copy Branch Name (#3635), safer terminal preset defaults (#3546), and /pricing page (#3639). Expand bug fixes with v2 git correctness, cross-fork PR misattribution, terminal paste/Unicode/Shift+Enter, and security bumps. * docs(changelog): add v2 public beta hero screenshot * docs(changelog): add Settings → Experimental screenshot, compress hero pngquant compression: v2-public-beta.png 704KB → 166KB (76%), v2-enable-flag.png 160KB → 36KB (78%). No visible quality loss. * docs(changelog): tighten v2 launch prose, condense bullet groups * docs(changelog): reframe cloud-first pillar as remote workspaces * docs(changelog): cut parallel-agents and honest-state pillars, fold into sub-sections * docs(changelog): tweak title and lead phrasing * docs(changelog): rewrite v2 launch lede around Twitter narrative Pull the launch story (physical limits, 3 ex-CTOs, cloud workspaces) into the lede, restructure pillars around Remote workspaces, Reimagined diff view, and Superset CLI, and add v2-remote-workspaces and v2-changes-pane screenshots to back the new sections. * docs(changelog): add CLI install snippet and docs link * docs(changelog): cut narrative lede, match standard changelog tone --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Kiet Ho <hoakiet98@gmail.com>
…-27) (#3792) * docs: generate weekly changelog 2026-04-27 * docs: reframe weekly changelog around v2 public beta Lead with v2 public beta + Settings → Experimental enable, restructure around the v1→v2 migration story, sidebar overhaul, cross-workspace terminals, and v2 chat. Pull in ~30 v2 PRs the bot missed and demote non-v2 items (Hosts page, marketing menu) to a brief "Also this week". * docs: pull in missed v2 features and bug fixes Add v2 project setup section (#3566, #3605, #3606, #3592, #3626, #3632), scheduled agent runs (#3576), Opus 4.7 (#3579), v1 review comments in pane (#3596), configurable v2 link-click (#3600), Copy Branch Name (#3635), safer terminal preset defaults (#3546), and /pricing page (#3639). Expand bug fixes with v2 git correctness, cross-fork PR misattribution, terminal paste/Unicode/Shift+Enter, and security bumps. * docs(changelog): add v2 public beta hero screenshot * docs(changelog): add Settings → Experimental screenshot, compress hero pngquant compression: v2-public-beta.png 704KB → 166KB (76%), v2-enable-flag.png 160KB → 36KB (78%). No visible quality loss. * docs(changelog): tighten v2 launch prose, condense bullet groups * docs(changelog): reframe cloud-first pillar as remote workspaces * docs(changelog): cut parallel-agents and honest-state pillars, fold into sub-sections * docs(changelog): tweak title and lead phrasing * docs(changelog): rewrite v2 launch lede around Twitter narrative Pull the launch story (physical limits, 3 ex-CTOs, cloud workspaces) into the lede, restructure pillars around Remote workspaces, Reimagined diff view, and Superset CLI, and add v2-remote-workspaces and v2-changes-pane screenshots to back the new sections. * docs(changelog): add CLI install snippet and docs link * docs(changelog): cut narrative lede, match standard changelog tone --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Kiet Ho <hoakiet98@gmail.com>
Summary
Test plan
Summary by cubic
Folder-first import now auto-creates a project using the folder’s basename when no cloud match is found, skipping the naming modal (v1 behavior). Name inference handles trailing slashes and Windows/UNC paths; users can still rename after import.
Refactors
FolderFirstImportModaland theno-matchstate flow;useFolderFirstImportnow returns{ start }only and runs import directly.FolderFirstImportModalusage inDashboardSidebarHeader.getBaseNametorenderer/lib/pathBasename, replaced duplicates, added unit tests, and fixed the trailing-separator edge case.Migration
useFolderFirstImport().startonly.FolderFirstImportModalandFolderFirstImportState.Written for commit cd2d7c3. Summary will update on new commits.
Summary by CodeRabbit