fix(projects): prevent duplicate project creation#2224
Conversation
Greptile SummaryThis PR fixes duplicate project creation triggered by lag-induced double-clicks. It adds a two-layer defense: a one-shot
Confidence Score: 4/5Safe to merge — the duplicate-creation bug is correctly addressed at two independent layers, and the pre-existing wrong-path bug in new/clone modes is fixed. Both fixes are mechanically correct and well-commented. The only concern is that isSubmittingRef is never reset, which silently relies on onClose() always being reached — a reasonable invariant today, but one that would silently break retry UX if error handling is added later. The submit guard logic in add-project-modal.tsx (lines 259–260) deserves a second look if error-handling UX is ever added to keep the modal open on failure.
|
| Filename | Overview |
|---|---|
| src/renderer/features/projects/components/add-project-modal/add-project-modal.tsx | Adds a one-shot useRef guard against double-submit and fixes path computation for new/clone modes (was incorrectly using pickState.path for all modes). The guard is intentionally non-resetting, relying on the invariant that onClose() is always called. |
| src/renderer/features/projects/stores/project-manager.ts | Adds _pendingCreationsByPath in-flight dedup map keyed by effective project path, and fixes _doCreateProject to use _effectiveProjectPath (full path for new/clone modes) rather than data.path directly. |
Sequence Diagram
sequenceDiagram
participant User
participant Modal as AddProjectModal
participant Store as ProjectManagerStore
participant RPC
User->>Modal: Click Submit (1st click)
Modal->>Modal: "isSubmittingRef.current = false → set true"
Modal->>RPC: inspectProjectPath(projectPath)
RPC-->>Modal: "{ existingProject: null }"
User->>Modal: Click Submit (2nd click, lag)
Modal->>Modal: "isSubmittingRef.current = true → return early"
Modal->>Store: createProject(type, data, id)
Store->>Store: _pendingCreationsByPath.get(pathKey) → null
Store->>Store: Store promise in _pendingCreationsByPath
Store->>RPC: _doCreateProject → inspectProjectPath(effectivePath)
RPC-->>Store: "{ existingProject: null }"
Store->>RPC: createProject(...)
RPC-->>Store: project created
Store->>Store: _pendingCreationsByPath.delete(pathKey)
Store-->>Modal: projectId
Note over Modal: If concurrent call reached store (bypassing ref guard),
Note over Modal: it would return the in-flight promise instead
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
src/renderer/features/projects/components/add-project-modal/add-project-modal.tsx:253-260
**One-shot guard creates a no-retry footgun**
`isSubmittingRef.current` is set to `true` on the very first submit attempt and never reset. The comment justifies this by saying "the modal always closes after a submit," which is currently true because every code path eventually calls `onClose()`. However, if a future change adds an error toast and early-returns (keeping the modal open after, e.g., a failed inspection), users will find the Submit button permanently silenced for that modal instance with no visible explanation. Resetting the ref in the `catch` block (before the fall-through) would remove this assumption without harming the dedup goal.
Reviews (1): Last reviewed commit: "fix(projects): prevent duplicate project..." | Re-trigger Greptile
summary
my macbook was lagging and through that it created two proejcts one healthy one and one corrupted one. this should fix that