feat(projects): add project creation wizard with enhanced UX#227
feat(projects): add project creation wizard with enhanced UX#227
Conversation
Add new project creation wizard component with improved user experience and better input field interactions. Integrate projects API routes on the backend to support CRUD operations for project management. Changes include: - Add ProjectCreationWizard component with step-by-step project setup - Improve input hint visibility to hide when user starts typing - Refactor project creation state management in Sidebar component - Add ReactDOM import for portal-based wizard rendering The wizard provides a more intuitive onboarding experience for users creating new projects, while the enhanced input hints reduce visual clutter during active typing.
WalkthroughAdds a protected Projects API and a new workspace creation flow: backend endpoints to validate/create workspaces and optionally clone GitHub repos; frontend modal wizard to guide workspace creation with token handling; API helper, sidebar integration, small UI and logging tweaks. Changes
Sequence DiagramsequenceDiagram
participant User
participant Sidebar
participant Wizard as ProjectCreationWizard
participant API as Frontend API
participant Server as Backend API
participant FS as FileSystem
participant Git as GitHub
User->>Sidebar: Click "New Project"
Sidebar->>Wizard: Open modal (portal)
Wizard->>API: GET /api/settings/credentials?type=github_token
API->>Server: Forward request
Server-->>API: Credentials list
API-->>Wizard: Tokens loaded
User->>Wizard: Choose workspace type, enter path (opt. Git URL)
Wizard->>API: Request path suggestions (if used)
API-->>Wizard: Suggestions
User->>Wizard: Confirm create
Wizard->>API: POST /api/projects/create-workspace {workspaceType, path, githubUrl?, tokenId?, newGithubToken?}
API->>Server: Forward creation request
alt workspaceType = "new"
Server->>FS: mkdir (recursive)
alt githubUrl provided
Server->>Git: git clone (inject token if provided)
Git-->>Server: clone success / error
alt clone error
Server->>FS: remove created dir (cleanup)
end
end
else workspaceType = "existing"
Server->>FS: verify path exists and is directory
end
Server->>Server: addProjectManually(register project)
Server-->>API: 200 Created (project data)
API-->>Wizard: Success
Wizard->>Sidebar: notify refresh / onProjectCreated
Wizard->>User: Close modal
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (2 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: 7
🧹 Nitpick comments (8)
server/index.js (1)
205-206: Consider consolidating duplicate/api/projectsroute definitions.A new
projectsRoutesrouter is mounted at/api/projects, but the file also contains direct route definitions for the same base path at lines 329-421 (GET /api/projects, GET /api/projects/:projectName/sessions, etc.). This creates organizational issues:
- Route precedence may not be obvious
- Difficult to maintain two different routing mechanisms for the same resource
- Potential for conflicts or unexpected behavior
Consider migrating the existing direct routes (lines 329-421) into the
projectsRoutesrouter module to consolidate all project-related endpoints in one place. This would improve code organization and maintainability.If the new router is meant to only handle new endpoints (like
/create-workspace), that should be documented, but consolidation is still recommended for long-term maintainability.src/components/ProjectCreationWizard.jsx (4)
64-84: Add debouncing to prevent excessive API calls during typing.The
loadPathSuggestionsfunction is called on every keystroke whenworkspacePath.length > 2. This can result in many rapid API calls as the user types, potentially causing performance issues and race conditions where responses arrive out of order.Consider adding a debounce mechanism to the useEffect that calls this function:
+ // Add at the top with other state + const [pathInputTimer, setPathInputTimer] = useState(null); useEffect(() => { + // Clear existing timer + if (pathInputTimer) { + clearTimeout(pathInputTimer); + } + if (workspacePath.length > 2) { - loadPathSuggestions(workspacePath); + const timer = setTimeout(() => { + loadPathSuggestions(workspacePath); + }, 300); // 300ms debounce + setPathInputTimer(timer); } else { setPathSuggestions([]); setShowPathDropdown(false); } + + return () => { + if (pathInputTimer) { + clearTimeout(pathInputTimer); + } + }; }, [workspacePath, loadPathSuggestions]);Alternatively, use a debounce library like lodash or a custom hook.
95-99: Add basic path validation to improve UX.The current validation only checks if the path is empty. Consider adding basic path format validation to catch obvious issues before submitting to the backend, such as:
- Ensuring the path starts with
/or~for absolute/home paths- Checking for dangerous system paths like
/,/etc,/bin, etc.- Validating that the path doesn't contain invalid characters
Example validation:
} else if (step === 2) { if (!workspacePath.trim()) { setError('Please provide a workspace path'); return; } + + // Basic path validation + const trimmedPath = workspacePath.trim(); + if (!trimmedPath.startsWith('/') && !trimmedPath.startsWith('~')) { + setError('Please provide an absolute path (starting with / or ~)'); + return; + } + + const dangerousPaths = ['/', '/bin', '/boot', '/dev', '/etc', '/lib', '/proc', '/root', '/sbin', '/sys', '/usr', '/var']; + if (dangerousPaths.includes(trimmedPath) || dangerousPaths.some(p => trimmedPath.startsWith(p + '/'))) { + setError('Cannot create workspace in system directories'); + return; + } // No validation for GitHub token - it's optional (only needed for private repos) setStep(3); }
125-127: Validate token ID before parsing.The code parses
selectedGithubTokento an integer without validation. If the value is somehow invalid,parseIntcould returnNaN, which would be sent to the backend.Add validation:
if (useExistingToken && selectedGithubToken) { - payload.githubTokenId = parseInt(selectedGithubToken); + const tokenId = parseInt(selectedGithubToken, 10); + if (isNaN(tokenId)) { + setError('Invalid token selected'); + setIsCreating(false); + return; + } + payload.githubTokenId = tokenId; } else if (!useExistingToken && newGithubToken) {
301-314: Add click-outside handler to close path suggestions dropdown.The path suggestions dropdown doesn't close when clicking outside of it, which can be confusing for users. Consider adding a click-outside handler or blur event.
Add a ref and click-outside effect:
+ const pathDropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (pathDropdownRef.current && !pathDropdownRef.current.contains(event.target)) { + setShowPathDropdown(false); + } + }; + + if (showPathDropdown) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [showPathDropdown]); - <div className="relative"> + <div className="relative" ref={pathDropdownRef}>server/routes/projects.js (3)
86-97: Handle case where both token sources are provided.The code doesn't explicitly handle the scenario where both
githubTokenIdandnewGithubTokenare provided. Currently,githubTokenIdtakes precedence due to the if-else structure, but this should be made explicit or validated.Add explicit validation:
// Get GitHub token if needed + if (githubTokenId && newGithubToken) { + await fs.rm(absolutePath, { recursive: true, force: true }); + return res.status(400).json({ + error: 'Please provide either githubTokenId or newGithubToken, not both' + }); + } + if (githubTokenId) {Alternatively, document the precedence in the API documentation comment at the top.
21-128: Consider adding concurrency control to prevent race conditions.The endpoint has no locking mechanism to prevent concurrent requests from interfering with each other. For example:
- Two requests to create a new workspace at the same path could both pass the existence check (lines 66-76) simultaneously
- Both could then attempt to create the directory, with one potentially failing
While this may be acceptable for a single-user application, it's worth considering for multi-user scenarios.
Consider adding in-memory locking for workspace creation operations:
// At module level const creationLocks = new Map(); // In the endpoint handler const lockKey = absolutePath; if (creationLocks.has(lockKey)) { return res.status(409).json({ error: 'A workspace creation operation is already in progress for this path' }); } creationLocks.set(lockKey, true); try { // ... existing creation logic ... } finally { creationLocks.delete(lockKey); }Alternatively, use a proper distributed lock if running multiple server instances.
133-143: Add error handling for database operations.The
getGithubTokenByIdfunction performs a dynamic import and database query without error handling. If the database connection fails or the query throws, the error will bubble up unhandled.Consider adding try-catch or documenting that the caller must handle errors:
async function getGithubTokenById(tokenId, userId) { + try { const { getDatabase } = await import('../database/db.js'); const db = await getDatabase(); const token = await db.get( 'SELECT * FROM github_tokens WHERE id = ? AND user_id = ? AND is_active = 1', [tokenId, userId] ); return token; + } catch (error) { + console.error('Error fetching GitHub token:', error); + throw new Error('Failed to retrieve GitHub token from database'); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
server/index.js(2 hunks)server/routes/projects.js(1 hunks)src/components/ChatInterface.jsx(1 hunks)src/components/ProjectCreationWizard.jsx(1 hunks)src/components/Sidebar.jsx(4 hunks)src/contexts/AuthContext.jsx(2 hunks)src/index.css(1 hunks)src/utils/api.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
server/index.js (1)
server/middleware/auth.js (1)
authenticateToken(22-45)
src/components/ProjectCreationWizard.jsx (5)
server/routes/projects.js (3)
workspacePath(23-23)token(88-88)token(137-140)src/utils/api.js (3)
api(23-146)api(23-146)token(3-3)server/index.js (2)
dirPath(426-426)token(175-176)src/components/ui/input.jsx (1)
Input(4-16)src/components/ui/button.jsx (1)
Button(35-43)
server/routes/projects.js (3)
server/index.js (2)
project(415-415)token(175-176)src/components/ProjectCreationWizard.jsx (5)
workspacePath(13-13)workspaceType(10-10)error(21-21)githubUrl(14-14)newGithubToken(17-17)src/utils/api.js (1)
token(3-3)
src/contexts/AuthContext.jsx (1)
src/utils/api.js (2)
api(23-146)api(23-146)
src/components/ChatInterface.jsx (1)
src/App.jsx (2)
sendByCtrlEnter(64-64)isInputFocused(56-56)
src/components/Sidebar.jsx (2)
src/components/ProjectCreationWizard.jsx (1)
ProjectCreationWizard(7-568)src/App.jsx (2)
isPWA(83-83)isMobile(53-53)
🔇 Additional comments (6)
src/contexts/AuthContext.jsx (1)
37-37: LGTM - Debug logging additions are helpful.The prefixed debug logs provide good visibility into the authentication flow for troubleshooting purposes.
Also applies to: 44-44, 75-75, 78-78
src/index.css (1)
82-82: Verify contrast and accessibility with the new input color.The
--inputcolor in dark mode changed from very dark (17.5% lightness) to medium gray (46% lightness). This significant increase could reduce text contrast on input fields or create visual inconsistencies with the dark theme.Please verify:
- Text contrast ratios meet WCAG AA standards (4.5:1 for normal text)
- Visual consistency with other dark mode UI elements
- Input field visibility and readability across different screens
You can test contrast ratios using browser DevTools or online tools like WebAIM's contrast checker.
src/utils/api.js (1)
74-78: LGTM - New API method follows established patterns.The
createWorkspacemethod is correctly implemented usingauthenticatedFetchand follows the same structure as other API methods in this file.src/components/ChatInterface.jsx (1)
4713-4726: Hint visibility update looks great.The opacity toggle cleanly hides hints while typing and keeps mobile guidance focused only when the field is active and empty. Nice polish.
src/components/Sidebar.jsx (1)
451-470: Portal integration looks solid.Rendering the wizard through a body-level portal gives the modal the space it needs, and the close/refresh hooks cover both dismissal paths.
src/components/ProjectCreationWizard.jsx (1)
498-507: Add safe access for token name lookup.Line 502 uses
find()to look up the token name, but if the token is not found, accessing.token_namewill throw an error. The code already has|| 'Unknown'but it won't work iffind()returnsundefined.The optional chaining is already present, so this should work correctly. However, for clarity, consider this alternative:
<span className="text-xs text-gray-900 dark:text-white"> {useExistingToken && selectedGithubToken - ? `Using stored token: ${availableTokens.find(t => t.id.toString() === selectedGithubToken)?.token_name || 'Unknown'}` + ? `Using stored token: ${availableTokens.find(t => t.id.toString() === selectedGithubToken)?.token_name ?? 'Unknown'}` : newGithubToken ? 'Using provided token' : 'No authentication'} </span>Note: Using
??(nullish coalescing) instead of||is more precise since it only checks fornull/undefined, not empty strings.Likely an incorrect or invalid review comment.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ub credentials implementation across components
…to feature/new-project-creation
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
server/routes/projects.js(1 hunks)src/components/ApiKeysSettings.jsx(5 hunks)src/components/ProjectCreationWizard.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
server/routes/projects.js (3)
server/index.js (1)
project(415-415)src/components/ProjectCreationWizard.jsx (5)
error(21-21)workspacePath(13-13)workspaceType(10-10)githubUrl(14-14)newGithubToken(17-17)src/components/ApiKeysSettings.jsx (1)
newGithubToken(14-14)
src/components/ProjectCreationWizard.jsx (3)
server/routes/projects.js (2)
workspacePath(164-164)token(238-238)src/utils/api.js (3)
api(23-146)api(23-146)token(3-3)server/index.js (2)
dirPath(426-426)token(175-176)
src/components/ApiKeysSettings.jsx (4)
server/routes/projects.js (1)
token(238-238)server/index.js (1)
token(175-176)src/components/ProjectCreationWizard.jsx (1)
newGithubToken(17-17)src/components/CredentialsSettings.jsx (1)
newGithubToken(14-14)
| if (githubToken) { | ||
| try { | ||
| const url = new URL(githubUrl); | ||
| // Format: https://[email protected]/user/repo.git | ||
| url.username = githubToken; | ||
| url.password = ''; | ||
| cloneUrl = url.toString(); | ||
| } catch (error) { | ||
| return reject(new Error('Invalid GitHub URL format')); | ||
| } | ||
| } | ||
|
|
||
| const gitProcess = spawn('git', ['clone', cloneUrl, destinationPath], { | ||
| stdio: ['ignore', 'pipe', 'pipe'], | ||
| env: { | ||
| ...process.env, | ||
| GIT_TERMINAL_PROMPT: '0' // Disable git password prompts | ||
| } | ||
| }); |
There was a problem hiding this comment.
Stop embedding GitHub tokens in the clone URL
Line 320 injects the personal access token into the HTTPS URL that is passed to git clone. That puts the token on the git process command line, in ps output, crash dumps, and any logging of the command. It reintroduces the very secret-leak vector we previously called out. Please switch to a safe credential flow (e.g., supply the token through GIT_ASKPASS or another git credential helper) so the token never appears in command arguments or logs. Let me know if you want help wiring up an askpass script.
| try { | ||
| const payload = { | ||
| workspaceType, | ||
| path: workspacePath.trim(), | ||
| }; | ||
|
|
||
| // Add GitHub info if creating new workspace with GitHub URL | ||
| if (workspaceType === 'new' && githubUrl) { | ||
| payload.githubUrl = githubUrl.trim(); | ||
|
|
||
| if (tokenMode === 'stored' && selectedGithubToken) { | ||
| payload.githubTokenId = parseInt(selectedGithubToken); | ||
| } else if (tokenMode === 'new' && newGithubToken) { | ||
| payload.newGithubToken = newGithubToken.trim(); | ||
| } | ||
| } | ||
|
|
||
| const response = await api.createWorkspace(payload); |
There was a problem hiding this comment.
Fix new-token flow when no stored credentials
Lines 125-129 only attach a token when tokenMode === 'new'. In the “no stored tokens” fallback UI we render an input for a one-off token, but tokenMode is still 'stored', so the payload omits the token and private clones 401. Repro: start with zero stored credentials, enter a private repo URL plus a PAT, click “Create” → backend receives no token. Please add a fallback so we still send newGithubToken when no stored tokens exist.
- if (tokenMode === 'stored' && selectedGithubToken) {
- payload.githubTokenId = parseInt(selectedGithubToken);
- } else if (tokenMode === 'new' && newGithubToken) {
- payload.newGithubToken = newGithubToken.trim();
- }
+ if (tokenMode === 'stored' && selectedGithubToken) {
+ payload.githubTokenId = parseInt(selectedGithubToken, 10);
+ } else if (tokenMode === 'new' && newGithubToken) {
+ payload.newGithubToken = newGithubToken.trim();
+ } else if (!availableTokens.length && newGithubToken) {
+ payload.newGithubToken = newGithubToken.trim();
+ }🤖 Prompt for AI Agents
In src/components/ProjectCreationWizard.jsx around lines 115 to 132, the
payload-building logic only attaches a GitHub token when tokenMode === 'new' or
when a stored token is selected, so in the “no stored tokens” fallback (UI shows
an input for a one-off token but tokenMode remains 'stored') the newGithubToken
is omitted; fix by changing the condition to also add newGithubToken when
tokenMode === 'new' OR when tokenMode === 'stored' but no selectedGithubToken
exists and newGithubToken is provided—trim the token, set payload.newGithubToken
= newGithubToken.trim(), and keep existing parseInt(selectedGithubToken)
behavior for stored tokens so the backend receives the one-off token in the
fallback case.
…ation feat(projects): add project creation wizard with enhanced UX
Add new project creation wizard component with improved user experience and better input field interactions. Integrate projects API routes on the backend to support CRUD operations for project management.
Changes include:
The wizard provides a more intuitive onboarding experience for users creating new projects, while the enhanced input hints reduce visual clutter during active typing.
Summary by CodeRabbit
New Features
UI Improvements