Skip to content

feat(projects): add project creation wizard with enhanced UX#227

Merged
viper151 merged 5 commits intomainfrom
feature/new-project-creation
Nov 4, 2025
Merged

feat(projects): add project creation wizard with enhanced UX#227
viper151 merged 5 commits intomainfrom
feature/new-project-creation

Conversation

@viper151
Copy link
Contributor

@viper151 viper151 commented Nov 4, 2025

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.

Summary by CodeRabbit

  • New Features

    • Project Creation Wizard: multi-step modal to create/configure workspaces (existing or new) with optional GitHub repo cloning.
    • Protected Projects API endpoints require authentication.
  • UI Improvements

    • Sidebar now opens the new Project Creation Wizard as a modal.
    • Dynamic hint text visibility in chat input with smooth transitions.
    • Settings: GitHub credentials UI updated (naming/endpoints reflected in the interface).
    • Dark theme input color adjusted.

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

coderabbitai bot commented Nov 4, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Backend: Projects API
server/index.js, server/routes/projects.js
Mounts a protected projectsRoutes at /api/projects. Implements POST /api/projects/create-workspace handling workspaceType ("existing"
Frontend: Project creation UI
src/components/ProjectCreationWizard.jsx, src/components/Sidebar.jsx
Adds ProjectCreationWizard modal (3-step flow: type, path/GitHub/config, confirm). Sidebar now launches the wizard via a portal and removes the prior inline new-project form and related state/effects. Wizard loads tokens, suggests paths, validates input, posts workspace payload to backend, and calls onProjectCreated on success.
API client & Settings
src/utils/api.js, src/components/ApiKeysSettings.jsx
Adds api.createWorkspace(workspaceData) POSTing to /api/projects/create-workspace. Updates GitHub credential endpoints and payload shapes in ApiKeysSettings to use /api/settings/credentials with fields credentialName, credentialType, credentialValue, and adjusted response field (credentials).
UI tweaks & logging
src/components/ChatInterface.jsx, src/index.css, src/contexts/AuthContext.jsx
Chat input hint visibility made conditional with transitions; dark-theme --input CSS color value changed; added debug/trace console logs inside AuthContext.checkAuthStatus.

Sequence Diagram

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

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing extra attention:
    • server/routes/projects.js: path validation, forbidden-dir checks, secure token handling, git clone error parsing, and cleanup behavior.
    • src/components/ProjectCreationWizard.jsx: multi-step state, token selection vs new token flow, API payload shape, and error handling.
    • src/components/Sidebar.jsx: portal integration and removed inline creation logic—verify no regressions in project list interactions.
    • API contract consistency between api.createWorkspace and backend expectations (tokenId vs newGithubToken, field names).

Possibly related PRs

  • Feature/edit diff #216 — Modifies project-related server routes under /api/projects; likely overlaps with mounting/changes to projects endpoints and should be reviewed for conflicts.

Poem

🐰 I hopped in to craft a workspace bright,
Paths and tokens snugly stored at night,
A wizard guides each careful clone,
From shell to repo, a project home,
Hop—new projects launched with delight!

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding a ProjectCreationWizard component with improved UX, which is the central feature across multiple file changes in this PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/new-project-creation

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.

Copy link
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.

Actionable comments posted: 7

🧹 Nitpick comments (8)
server/index.js (1)

205-206: Consider consolidating duplicate /api/projects route definitions.

A new projectsRoutes router 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 projectsRoutes router 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 loadPathSuggestions function is called on every keystroke when workspacePath.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 selectedGithubToken to an integer without validation. If the value is somehow invalid, parseInt could return NaN, 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 githubTokenId and newGithubToken are provided. Currently, githubTokenId takes 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:

  1. Two requests to create a new workspace at the same path could both pass the existence check (lines 66-76) simultaneously
  2. 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 getGithubTokenById function 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

📥 Commits

Reviewing files that changed from the base of the PR and between 401223d and 43cbbb1.

📒 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 --input color 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:

  1. Text contrast ratios meet WCAG AA standards (4.5:1 for normal text)
  2. Visual consistency with other dark mode UI elements
  3. 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 createWorkspace method is correctly implemented using authenticatedFetch and 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_name will throw an error. The code already has || 'Unknown' but it won't work if find() returns undefined.

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 for null/undefined, not empty strings.

Likely an incorrect or invalid review comment.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@siteboon siteboon deleted a comment from coderabbitai bot Nov 4, 2025
@viper151 viper151 merged commit 519b5e5 into main Nov 4, 2025
1 check was pending
@viper151 viper151 deleted the feature/new-project-creation branch November 4, 2025 09:38
Copy link
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.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43cbbb1 and 7ab1475.

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

Comment on lines +316 to +334
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
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +115 to +132
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

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