Skip to content

updated the new workspace stuff and added clone from url#82

Closed
AviPeltz wants to merge 3 commits intomainfrom
basic-ui-polishing
Closed

updated the new workspace stuff and added clone from url#82
AviPeltz wants to merge 3 commits intomainfrom
basic-ui-polishing

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Nov 14, 2025

Description

Related Issues

Type of Change

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

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • New Features

    • Clone a repository from a URL with validation and clearer error reporting
    • Directory picker to choose destination folders
  • UI/UX Improvements

    • New/updated clone-from-URL dialog with input validation and progress/errors
    • Redesigned workspace selection modal with recent projects and keyboard navigation
    • Dialog overlay can be customized via new styling prop
  • Refactor

    • Widespread formatting, import/type reordering, and minor styling updates (fonts added)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 14, 2025

Walkthrough

Adds Git clone support via new IPC handlers and a WorktreeManager clone method, introduces a CloneFromUrlDialog and workspace-selection UI changes, extends workspace IPC channel types, and applies widespread formatting/whitespace cleanups across many files.

Changes

Cohort / File(s) Summary
Git cloning core
apps/desktop/src/main/lib/worktree-manager.ts, apps/desktop/src/main/lib/workspace-ipcs.ts, apps/desktop/src/shared/ipc-channels/workspace.ts
New cloneRepository(url,destinationPath) method; workspace-clone-from-url and workspace-select-directory IPC handlers added; workspace IPC channel types extended for cloning and directory selection.
Workspace selection UI
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/CloneFromUrlDialog.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/index.ts
New CloneFromUrlDialog component; WorkspaceSelectionModal redesigned with action cards, keyboard navigation, clone flow wiring to IPC; index export removed.
Dialog overlay API
apps/desktop/src/renderer/components/ui/dialog.tsx
Added optional overlayClassName?: string prop to DialogContent and applied to overlay element.
AddTaskModal & task list
apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/*
Reordered exports/imports, removed duplicate imports, expanded TaskListProps with task state/controls, adjusted cloneTabsFromWorktreeId auto-select/clear logic; mostly formatting.
Main layout change
apps/desktop/src/renderer/screens/main/components/Layout/NewLayoutMain.tsx
Removed loading from the PlaceholderState gating condition (now shows placeholder only on error or missing selections).
Formatting / trivial edits
multiple renderer, main, shared, and website files (e.g., apps/desktop/src/main/lib/window-manager.ts, apps/desktop/src/main/lib/window-state-manager.ts, many apps/desktop/src/renderer/**, apps/desktop/src/shared/ipc-channels/*, apps/website/src/app/*, packages/ui/src/components/dropdown-menu.tsx)
Large set of whitespace/line-break/ import-order/ trailing-newline formatting changes with no semantic change.
Styles & font
apps/desktop/src/renderer/globals.css, apps/desktop/src/renderer/index.html
Added .font-micro5 CSS utility (duplicated in two scopes) and Google Fonts links to load "Micro 5".
Page state updates
apps/website/src/app/page.tsx
Added isWaitlistOpen state, passed onOpenWaitlist into FeaturesSection, and rendered WaitlistModal.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CloneDialog as CloneFromUrlDialog
    participant Renderer
    participant IPC as Main Process (ipcMain)
    participant Worktree as WorktreeManager
    participant WorkspaceStore as Workspace creation

    User->>CloneDialog: Enter URL + destination, submit
    CloneDialog->>Renderer: validate inputs
    CloneDialog->>IPC: invoke "workspace-clone-from-url" with {url,destinationPath}
    IPC->>Worktree: call cloneRepository(url,destinationPath)
    
    alt clone success
        Worktree->>WorkspaceStore: create or reuse workspace
        WorkspaceStore-->>IPC: workspace data
        IPC-->>CloneDialog: { success: true, data: Workspace }
        CloneDialog->>User: close, select workspace
    else clone failure
        Worktree-->>IPC: { success: false, error }
        IPC-->>CloneDialog: { success: false, error }
        CloneDialog->>User: display error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Areas to focus review on:

  • Workspace clone IPC and WorktreeManager.cloneRepository: validate input sanitization, error-message extraction, and repo validation steps.
  • WorkspaceSelectionModal <> CloneFromUrlDialog integration: state transitions, keyboard navigation, and selection side effects.
  • New IPC channel typings in shared/ipc-channels/workspace.ts matching handler implementations.
  • The NewLayoutMain conditional change (omitting loading) to confirm UX intent.

Possibly related PRs

  • initial ws #77 — Adds/extends workspace IPC handlers and worktree repository operations; directly related to the clone IPC and WorktreeManager changes.
  • workspace #71 — Changes window/workspace management APIs (e.g., setWorkspaceForWindow); touches same window-manager area reformatted here.
  • Fix modal #63 — Modifies NewLayoutMain rendering logic; related to the loading-state gating change present in this diff.

Poem

🐰 I nibble code, I hop and hum,

URLs land and clones become,
Dialogs shine with tidy seams,
Workspaces sprout from Git-born dreams,
Hooray — a rabbit’s tidy streams! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the repository template with no substantive content filled in—all sections (Description, Related Issues, Testing, Screenshots, Additional Notes) are blank except for Type of Change boxes. Provide a detailed description of changes: summarize the new features (clone from URL IPC handler, CloneFromUrlDialog component), refactored components, and key file modifications. Include related issues, testing steps, and any breaking changes or migration notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title partially describes the changeset—it mentions 'clone from url' which relates to new IPC handlers and dialog components added, and 'workspace stuff' which aligns with the overall workspace-related changes, but it lacks specificity about the scope of work.
✨ 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 basic-ui-polishing

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

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.

Actionable comments posted: 1

🧹 Nitpick comments (7)
apps/desktop/src/renderer/screens/main/utils.ts (2)

7-27: Tab lookup helper matches current tab model

findTabRecursive correctly:

  • guards against undefined tab arrays, and
  • searches top-level tabs plus one level of type === "group" children, returning the parent for child matches.

Given the current Tab model explicitly disallows nested group tabs, this implementation is sufficient and straightforward.

If you ever relax that constraint and allow deeper nesting, this would be a natural spot to refactor into a truly recursive search to avoid duplicating the child-loop pattern elsewhere.


30-87: Worktree enrichment logic is sound; consider stabilizing pending metadata

The enrichment flow looks good:

  • Pending worktrees are projected into WorktreeWithTask with isPending: true, empty tabs, and optional task derived from pending.taskData.
  • Real worktrees are matched by branch against MOCK_TASKS, and enriched with a task that includes assignee metadata when found.
  • The final merged array preserves type consistency by returning both pending and real worktrees as WorktreeWithTask.

Two minor, optional considerations:

  1. createdAt for pending worktrees is recomputed with new Date().toISOString() on every call, so if you ever surface this timestamp in the UI or use it for stable sorting, you may want to store it on PendingWorktree or pass it in rather than regenerating it.
  2. If branches can collide across environments, you might eventually want a more robust task–worktree association (e.g., explicit task ID on the worktree), but for MOCK_TASK-based enrichment this is fine.
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/CloneFromUrlDialog.tsx (1)

31-55: Tighten input handling (trim values and clear errors on edit)

The overall dialog looks solid and the IPC integration is straightforward. A couple of small tweaks would make the UX more robust and avoid subtle failures with copied URLs/paths that contain trailing spaces:

  • Trim url and destinationName before validating and passing them to onClone, and use the trimmed values when deciding if the Clone button should be enabled.
  • Clear error not only when the URL changes, but also when the destination path is edited, so the user sees immediate feedback when correcting mistakes.

Concretely, something like this would help:

 const handleUrlChange = (newUrl: string) => {
-	setUrl(newUrl);
+	const trimmedUrl = newUrl.trim();
+	setUrl(trimmedUrl);
 	setError(null);

 	// Auto-populate destination name from URL
-	const repoName = extractRepoName(newUrl);
+	const repoName = extractRepoName(trimmedUrl);
 	if (repoName && !destinationName) {
 		setDestinationName(repoName);
 	}
 };
@@
 						<div className="flex gap-2">
 							<Input
 								id="destination"
@@
-								value={destinationName}
-								onChange={(e) => setDestinationName(e.target.value)}
+								value={destinationName}
+								onChange={(e) => {
+									setDestinationName(e.target.value);
+									setError(null);
+								}}
@@
-	const handleClone = async () => {
-		if (!url || !destinationName) {
+	const handleClone = async () => {
+		const trimmedUrl = url.trim();
+		const trimmedDestination = destinationName.trim();
+
+		if (!trimmedUrl || !trimmedDestination) {
 			setError("Please provide both URL and destination name");
 			return;
 		}
@@
-		try {
-			await onClone(url, destinationName);
+		try {
+			await onClone(trimmedUrl, trimmedDestination);
@@
 					<Button
 						onClick={handleClone}
-						disabled={isLoading || !url || !destinationName}
+						disabled={
+							isLoading || !url.trim() || !destinationName.trim()
+						}

This keeps the current behavior but avoids treating whitespace-only inputs as valid and provides clearer feedback as the user fixes input errors.

Also applies to: 56-74, 76-99, 117-124, 132-139, 174-179

apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (2)

30-53: IPC response handling in handleCloneFromUrl looks correct, but consider typing the response

The success/error branching and rethrowing here works well with CloneFromUrlDialog’s onClone contract (errors bubble up to the dialog for display, success selects the workspace). To avoid any-shaped IPC responses getting out of sync with the main process, consider adding a shared TS type for the "workspace-clone-from-url" response and asserting/casting result to it at the call site, e.g. a { success: boolean; data?: Workspace; error?: string } type reused from the IPC channel definition.


59-151: Modal is now effectively non-dismissible; confirm this UX is intentional

With open={isOpen}, onOpenChange={() => {}}, and showCloseButton={false}, the workspace selection modal can’t be closed via Esc, backdrop click, or a close button; only the parent can change isOpen. If users should be able to dismiss this screen, wire onOpenChange to a parent close handler instead (or accept an onOpenChange prop and pass it through). If it’s intentionally “blocking until workspace chosen”, this is fine as-is.

apps/desktop/src/main/lib/workspace-ipcs.ts (2)

84-104: Directory-select IPC matches renderer contract; watch out for downstream path joining

The shape { canceled: boolean; filePath?: string } aligns with how CloneFromUrlDialog uses the result. However, the renderer currently constructs the final destination with string concatenation (${result.filePath}/${repoName}), which can produce mixed separators on Windows. Consider moving the “append repo name to directory” logic into this handler (using Node’s path.join) or introducing a shared helper, so all path composition happens on the main process with proper cross-platform handling.


106-179: Clone-from-URL flow is solid; improve repo name derivation and reuse existing logic

The sequence—clone, validate git repo, determine current branch, reuse existing workspace if any, otherwise create and return one—is sound and returns a clean { success, data, error } envelope expected by the renderer.

Two improvement points:

  1. Cross-platform repo name
    const repoName = repoPath.split("/").pop() || "Repository"; will not behave well on Windows paths. Prefer Node’s path.basename (and ideally reuse the same helper wherever you derive repo names). For example:

    import path from "path"; // at top of file
    
    const repoName = path.basename(repoPath) || "Repository";
  2. Share typing with IPC definition
    If you have a shared IPC response type for "workspace-clone-from-url", return that type explicitly here and in the renderer, rather than an ad-hoc object, to avoid future drift between main and renderer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd128c and ff96c15.

📒 Files selected for processing (74)
  • apps/desktop/src/main/lib/window-manager.ts (4 hunks)
  • apps/desktop/src/main/lib/window-state-manager.ts (1 hunks)
  • apps/desktop/src/main/lib/workspace-ipcs.ts (3 hunks)
  • apps/desktop/src/main/lib/worktree-manager.ts (2 hunks)
  • apps/desktop/src/renderer/components/ui/dialog.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/AppFrame/AppFrame.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/DiffView/DiffContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/DiffView/DiffContentArea.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskForm.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskData.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/NewLayoutMain.tsx (8 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/AddTaskButton.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/ModeToggle.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/TaskTabs.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/WorktreeTabButton.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/MainContentArea/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/ModeCarousel.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/AnimatedBackground/AnimatedBackground.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/AnimatedBackground/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeContent/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeHeader/ModeHeader.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeHeader/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeNavigation/ModeNavigation.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeNavigation/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/constants.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/hooks/useModeDetection.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/hooks/useScrollProgress.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/hooks/useScrollSnap.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/types.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeSwitcher/ModeSwitcher.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeSwitcher/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/WorktreeList.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx (5 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/components/TabItem/TabItem.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/SidebarOverlay/SidebarOverlay.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/SidebarOverlay/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/CloneFromUrlDialog.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (3 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/constants.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useDiffData.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useSidebar.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useTabs.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useTasks.ts (6 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useWorkspace.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useWorktrees.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/types.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/utils.ts (1 hunks)
  • apps/desktop/src/shared/ipc-channels.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/deep-link.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/external.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/index.ts (1 hunks)
  • apps/desktop/src/shared/ipc-channels/proxy.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/tab.ts (1 hunks)
  • apps/desktop/src/shared/ipc-channels/terminal.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/types.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/window.ts (0 hunks)
  • apps/desktop/src/shared/ipc-channels/workspace.ts (1 hunks)
  • apps/desktop/src/shared/ipc-channels/worktree.ts (0 hunks)
  • apps/website/src/app/components/HeroSection/HeroSection.tsx (1 hunks)
  • apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx (1 hunks)
  • apps/website/src/app/page.tsx (1 hunks)
  • packages/ui/src/components/dropdown-menu.tsx (1 hunks)
💤 Files with no reviewable changes (27)
  • apps/desktop/src/renderer/screens/main/hooks/useTabs.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/WorktreeList.tsx
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/index.ts
  • apps/desktop/src/renderer/screens/main/hooks/useSidebar.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/MainContentArea/index.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/AnimatedBackground/index.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeNavigation/index.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeContent/index.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/index.ts
  • apps/desktop/src/shared/ipc-channels/deep-link.ts
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskData.ts
  • apps/desktop/src/shared/ipc-channels/window.ts
  • apps/desktop/src/shared/ipc-channels/external.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/hooks/useScrollProgress.ts
  • apps/desktop/src/shared/ipc-channels/worktree.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeHeader/ModeHeader.tsx
  • apps/desktop/src/shared/ipc-channels.ts
  • apps/desktop/src/shared/ipc-channels/types.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/constants.ts
  • apps/desktop/src/shared/ipc-channels/proxy.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeSwitcher/ModeSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/hooks/useScrollSnap.ts
  • apps/desktop/src/shared/ipc-channels/terminal.ts
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeSwitcher/index.ts
  • apps/desktop/src/renderer/screens/main/components/SidebarOverlay/index.ts
🧰 Additional context used
🧬 Code graph analysis (8)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/ModeCarousel.tsx (1)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeContent/ModeContent.tsx (1)
  • ModeContent (11-24)
apps/desktop/src/renderer/screens/main/utils.ts (3)
apps/desktop/src/shared/types.ts (2)
  • Tab (44-56)
  • Worktree (58-68)
apps/desktop/src/renderer/screens/main/types.ts (1)
  • PendingWorktree (17-28)
apps/desktop/src/renderer/screens/main/constants.ts (1)
  • MOCK_TASKS (4-178)
apps/desktop/src/shared/ipc-channels/workspace.ts (2)
apps/desktop/src/shared/ipc-channels/types.ts (2)
  • IpcResponse (8-12)
  • NoRequest (17-17)
apps/desktop/src/shared/types.ts (1)
  • Workspace (70-82)
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/CloneFromUrlDialog.tsx (1)
  • CloneFromUrlDialog (21-184)
apps/desktop/src/renderer/components/ui/dialog.tsx (1)
apps/desktop/src/renderer/lib/utils.ts (1)
  • cn (4-6)
apps/desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx (6)
apps/desktop/src/renderer/screens/main/types.ts (1)
  • AppMode (30-30)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/types.ts (1)
  • SidebarMode (4-4)
apps/desktop/src/renderer/screens/main/hooks/useDiffData.ts (1)
  • useDiffData (12-199)
apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1)
  • Sidebar (25-411)
apps/desktop/src/renderer/screens/main/components/PlaceholderState.tsx (1)
  • PlaceholderState (7-44)
apps/desktop/src/renderer/screens/main/components/DiffView/DiffContentArea.tsx (1)
  • DiffContentArea (27-373)
apps/desktop/src/main/lib/workspace-ipcs.ts (1)
apps/desktop/src/main/lib/workspace-manager.ts (1)
  • workspaceManager (481-481)
apps/desktop/src/renderer/screens/main/hooks/useTasks.ts (1)
apps/desktop/src/renderer/screens/main/types.ts (1)
  • PendingWorktree (17-28)
🔇 Additional comments (54)
apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/AddTaskButton.tsx (1)

18-23: Clean formatting restructuring with no functional changes.

The Button component props are now distributed across lines for improved readability. The change is purely stylistic—all props and event handlers remain identical, and component behavior is unaffected.

apps/website/src/app/components/HeroSection/HeroSection.tsx (1)

1-3: Import reordering is unrelated to PR objectives.

The import statement for HeroParallax on line 3 has been reordered, but this change appears unrelated to the PR's stated goal of adding workspace cloning and Git functionality. While the import reordering itself has no functional impact, it introduces minor scope creep. Consider reverting this change to keep the PR focused on its core objectives, or clarify in the PR description if import organization is part of the intended changes.

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx (1)

230-230: LGTM!

The formatting adjustments to the ternary conditional and prop multiline presentation are purely cosmetic with no functional impact. The component logic, state management, and prop passing remain unchanged and correct.

Also applies to: 252-254

apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/WorktreeItem.tsx (1)

15-15: Formatting and indentation improvements look good.

The reformatted imports and adjusted multi-line template strings for classNames improve code readability and consistency. These purely cosmetic changes maintain the same runtime behavior and logic.

Also applies to: 198-204, 263-265, 747-747, 893-893

apps/desktop/src/renderer/screens/main/components/AppFrame/AppFrame.tsx (1)

6-6: Consider the purpose and scope of this formatting change.

Line 6 converts the multi-line JSX return to a single line. While the output is functionally identical, this is purely cosmetic and unrelated to the PR's stated objective of adding Git cloning and workspace features.

Verify:

  • Whether your project's style guide mandates single-line JSX for simple components, or if multi-line is preferred for readability.
  • Whether the resulting line length complies with your linter's line-length threshold (the className attribute is non-trivial).

If there is no style rationale, consider reverting this to reduce formatting noise and keep the diff focused on feature work.

apps/desktop/src/renderer/screens/main/constants.ts (1)

5-177: Formatting change: tabs instead of spaces.

This is a whitespace-only reformatting of the MOCK_TASKS array with no functional impact.

apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/WorktreeItem/components/TabItem/TabItem.tsx (1)

141-147: Formatting-only refactor; behavior and styles preserved

The ternary logic and the emitted class strings are unchanged; only the string interpolation is wrapped for readability. No issues from a behavior or styling standpoint.

apps/desktop/src/renderer/components/ui/dialog.tsx (1)

47-81: LGTM! Clean implementation of overlay customization.

The addition of the overlayClassName prop enables customization of the dialog overlay styling while maintaining backward compatibility. The implementation correctly:

  • Types the prop as optional
  • Passes it through to DialogOverlay
  • Leverages the existing cn() utility for class merging
apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts (2)

3-3: LGTM!

Formatting adjustment to the import statement with no functional impact.


48-50: LGTM!

The multi-line formatting of the find call improves readability without affecting functionality.

apps/website/src/app/components/HeroSection/components/HeroCanvas/components/LitBackground/LitBackground.tsx (2)

10-10: Import changes look good.

The addition of GEOMETRY_CONFIG is appropriate for the usage in the planeGeometry configuration (lines 170-173). The reordering of the shader imports is purely cosmetic and has no functional impact.

Also applies to: 16-17


169-175: Usage of GEOMETRY_CONFIG is correct, but note PR scope concern.

The implementation correctly uses GEOMETRY_CONFIG properties to configure the planeGeometry. Extracting these magic numbers into a centralized configuration is a good practice.

However, this file appears unrelated to the PR objectives ("updated the new workspace stuff and added clone from url"). This website hero section code doesn't align with the described workspace cloning functionality. Consider verifying whether this file was intentionally included in this PR.

apps/website/src/app/page.tsx (1)

1-31: Implementation verified—confirm this change belongs in this PR.

The component prop contracts are satisfied correctly:

  • FeaturesSection properly receives and uses onOpenWaitlist callback
  • WaitlistModal properly receives and uses isOpen and onClose props
  • State management flow is correct

However, this file still implements waitlist modal functionality for a website landing page, which appears unrelated to the PR's stated objectives about "workspace stuff and clone from url". Before approving, verify that bundling this website change into the same PR is intentional. If unrelated, separate it into a distinct PR to improve reviewability and reduce merge complexity.

apps/desktop/src/shared/ipc-channels/index.ts (1)

14-31: Import-order tweak is safe

Reordering the WorktreeChannels import keeps the IpcChannels extension list unchanged and has no behavioral impact. All channel types remain correctly combined into IpcChannels.

packages/ui/src/components/dropdown-menu.tsx (1)

7-260: Dropdown menu wrappers remain thin and consistent

All wrappers still transparently forward Radix props, add data-slot hooks, and compose Tailwind classes consistently. No prop/behavior changes jump out, and the defaulted props (modal, sideOffset, etc.) look intentional.

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx (1)

2-2: ScrollArea import cleanup looks good

Using the single ScrollArea import from @superset/ui/scroll-area and removing the duplicate keeps the module tidy without changing runtime behavior.

apps/desktop/src/renderer/screens/main/hooks/useWorkspace.ts (1)

95-97: Window restore check formatting-only change

The isRestored assignment now reads on one line but still awaits window-is-restored before deciding whether to show the workspace selection modal. No behavioral change introduced.

apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/ModeNavigation/ModeNavigation.tsx (1)

36-43: ClassName rewrap preserves behavior

The template literal for the mode button keeps the same classes and active/inactive branches; only line breaks changed. Hover/active styling and click handling are unaffected.

apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/TaskTabs.tsx (1)

53-55: Tab width calculation unchanged

const availableWidth = containerWidth - gap * (numTabs - 1); relies on standard operator precedence and is equivalent to the parenthesized form. The min/max clamping and resize handling remain intact.

apps/desktop/src/renderer/screens/main/hooks/useWorktrees.ts (1)

165-180: Worktree selection fallback condition remains correct

The reflowed condition around refreshedWorkspace.worktrees preserves the original semantics and still guards against empty/undefined worktree arrays before selecting the first entry. No issues here.

apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/ModeToggle.tsx (1)

14-18: Conditional className formatting is fine

The reformatted template literals keep the mode === "plan" | "edit" checks and resulting classes intact while improving readability. No behavioral change.

Also applies to: 25-29

apps/desktop/src/renderer/screens/main/components/DiffView/DiffContent.tsx (1)

158-166: Diff status badge ternary unchanged in behavior

The multi-line ternary for file.status now reads more clearly but still maps each status to the same classes as before. Looks good.

apps/desktop/src/main/lib/window-state-manager.ts (1)

135-138: Error logging change is cosmetic only

Wrapping the console.error call and passing error as a separate argument preserves behavior while improving readability. No further changes needed.

apps/desktop/src/renderer/screens/main/components/MainContentArea/MainContentArea.tsx (1)

2-5: Diff vs tabs layout wiring looks consistent; confirm autoSaveId change is intentional

  • The integration of sidebarMode and selectedFile with Sidebar and DiffContentArea is coherent:
    • useDiffData is gated via enabled: sidebarMode === "changes" && !!selectedWorktreeId, so diff data isn’t fetched unnecessarily.
    • The main render branches correctly between:
      • Diff mode (loading placeholder → DiffContentArea → empty placeholder), and
      • Workspace/tab mode (placeholder vs TabGroup / TabContent), with appropriate guards on currentWorkspace, selectedWorktree, and selectedTab.
  • The early if (mode === "plan") return to PlanView keeps plan mode isolated and avoids rendering the resizable layout, which is sensible.

One behavioral detail to double-check: ResizablePanelGroup now uses autoSaveId="new-layout-panels". If this differs from the previous ID, it will reset any persisted panel sizes for existing users. That’s not wrong, but worth confirming it’s deliberate (e.g., to clear out legacy layout state after structural changes).

Also applies to: 19-40, 42-220

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx (4)

13-29: Task type import from ./types is appropriate

Importing Task from the local ./types module to back the existing TaskListProps usage keeps this component strongly typed and localized. No issues here.


91-98: Loading-state UI reformat is behaviorally identical

The Loader2 block is just re-indented; size and animation classes are the same, so users will see the same “Loading tasks…” experience.


106-108: Empty-state message remains clear

The "No tasks match your search" vs "No tasks found" messages are unchanged in content; only the ternary formatting moved. Readability is fine and behavior is preserved.


146-149: Open-task button behavior unchanged and sensible

The primary footer button still triggers onOpenTask and is disabled when there’s no selectedTask or while tasks are loading. The JSX reflow doesn’t alter behavior and keeps UX safeguards in place.

apps/desktop/src/shared/ipc-channels/tab.ts (1)

5-10: Import formatting change only

Reformatting the type import to multi-line is fine and keeps the TabChannels surface unchanged; no issues here.

apps/desktop/src/renderer/screens/main/components/Layout/TaskTabs/WorktreeTabButton.tsx (1)

26-41: JSX and className formatting; behavior unchanged

The updated formatting for the wrapper div, button className conditionals, and tab label ternary preserves existing behavior (selected styling, pending state, and branch/task title display). Looks good.

Also applies to: 59-61, 77-80

apps/desktop/src/renderer/screens/main/hooks/useDiffData.ts (1)

109-112: Diff loading guards and updates remain correct

The multi-line guard for already-loaded/loading file IDs and the ternary when updating file changes are semantically identical to the prior logic and remain correct for preventing duplicate loads and safely defaulting changes to an array.

Also applies to: 153-154

apps/desktop/src/renderer/screens/main/components/SidebarOverlay/SidebarOverlay.tsx (1)

12-15: onUpdateWorktree prop typing reformat is safe

Reformatting the onUpdateWorktree type and using the inline import("shared/types").Worktree keeps the signature and usage the same; no issues here.

apps/desktop/src/renderer/screens/main/components/DiffView/DiffContentArea.tsx (1)

52-57: File auto-load and IntersectionObserver guards unchanged

The reformatted conditions for eagerly loading the selected file and lazily loading via IntersectionObserver keep the original semantics (avoid reloading already loaded/loading files and only operate in “files” view). No behavioral changes or issues.

Also applies to: 64-65, 72-76

apps/desktop/src/renderer/screens/main/components/Layout/NewLayoutMain.tsx (6)

243-246: LGTM!

Formatting change only—the type annotation remains unchanged.


531-534: LGTM!

Formatting improvement for readability—logic remains correct.


723-726: LGTM!

Event listener cleanup formatting improved—no behavioral change.

Also applies to: 773-776


759-762: LGTM!

Logging statement reformatted—no functional change.


589-589: LGTM!

Whitespace adjustment for readability.


1051-1055: Verify that removing loading from the gating condition is safe.

The loading state no longer prevents the main content panel from rendering. While the remaining guards (!currentWorkspace, !selectedTab, !selectedWorktree) should protect against incomplete data, ensure there are no race conditions where these values are truthy while data is still being populated during initial load.

apps/desktop/src/main/lib/window-manager.ts (1)

10-13: LGTM!

All changes are formatting-only adjustments to improve code readability—no functional changes.

Also applies to: 59-59, 81-84, 116-119

apps/desktop/src/main/lib/worktree-manager.ts (1)

857-860: LGTM!

Formatting improvement for readability—logic unchanged.

apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1)

8-8: LGTM!

Import statement consolidated to a single line—no functional change.

apps/desktop/src/renderer/screens/main/types.ts (1)

5-27: LGTM!

Type definitions reformatted for consistency—no changes to field names or types.

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts (1)

1-2: LGTM!

Import order adjusted and trailing whitespace removed—no functional changes.

apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/components/AnimatedBackground/AnimatedBackground.tsx (1)

22-22: LGTM!

Added trailing comma (best practice) and removed trailing blank line—no functional changes.

apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/ModeCarousel.tsx (1)

94-94: LGTM!

Component usage consolidated to a single line for consistency—no prop or rendering changes.

apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/hooks/useModeDetection.ts (1)

39-53: Consolidated bounds check remains correct

The combined condition still properly guards against out-of-range indices and missing modes before accessing modes[finalIndex], so behavior is unchanged and safe.

apps/desktop/src/renderer/screens/main/components/Sidebar/components/ModeCarousel/index.ts (1)

2-2: Type export reordering is harmless

Reordering ModeCarouselProps and SidebarMode in the type export does not change the public API shape; named imports continue to work as before.

apps/desktop/src/renderer/screens/main/hooks/useTasks.ts (1)

5-5: Formatting and type annotations look good

These changes (type import order, multiline generics, and reflowed ipcRenderer.removeListener / progress-guard code) are formatting-only and preserve the existing listener lifecycle and task creation behavior.

Also applies to: 24-26, 34-36, 43-46, 176-180, 190-193, 266-269

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskForm.tsx (1)

83-85: UI/text formatting changes are non-functional

Wrapping (Optional) in a span and reflowing handlers/props just improves readability and styling; component behavior and form wiring remain unchanged.

Also applies to: 125-127, 171-175, 211-215

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts (1)

5-5: Type export reorder is safe

Reordering APITask and Task in the exported type list does not affect consumers; the same types are still available from this barrel.

apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts (1)

10-14: Formatting-only changes; logic unchanged

Line breaks in formatRelativeTime and the multiline function signature for generateBranchNameWithCollisionAvoidance don’t alter behavior; both utilities continue to work as before.

Also applies to: 33-35

apps/desktop/src/shared/ipc-channels/workspace.ts (1)

114-122: New workspace IPC channels are well-shaped

The workspace-clone-from-url and workspace-select-directory channel definitions follow the existing IPC conventions (IpcResponse<Workspace> for workspace mutations and a simple result object for selection dialogs) and provide clear request/response shapes for the new clone-from-URL flow.

apps/desktop/src/main/lib/workspace-ipcs.ts (1)

727-729: detectMainBranch call reformatting is harmless

This is a formatting-only change; the call semantics to worktreeManager.detectMainBranch are unchanged and remain correct.

Comment on lines +1335 to +1388
/**
* Clone a git repository from a URL
*/
async cloneRepository(
url: string,
destinationPath: string,
): Promise<{ success: boolean; path?: string; error?: string }> {
try {
// Validate URL format (basic check)
if (
!url.includes("github.com") &&
!url.includes("gitlab.com") &&
!url.includes("bitbucket.org")
) {
// Accept common git hosting services or any URL with .git
if (!url.endsWith(".git") && !url.startsWith("git@")) {
return {
success: false,
error:
"Invalid repository URL. Please provide a valid git repository URL.",
};
}
}

// Clone the repository
await execAsync(`git clone "${url}" "${destinationPath}"`, {
encoding: "utf-8",
});

return {
success: true,
path: destinationPath,
};
} catch (error) {
console.error("Failed to clone repository:", error);

// Extract a cleaner error message from git output
let errorMessage = error instanceof Error ? error.message : String(error);

// Try to extract the fatal/error line from stderr for a cleaner message
if (typeof error === "object" && error !== null && "stderr" in error) {
const stderr = String((error as any).stderr);
const fatalMatch = stderr.match(/fatal: (.+)/);
if (fatalMatch) {
errorMessage = fatalMatch[1];
}
}

return {
success: false,
error: errorMessage,
};
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Potential shell injection vulnerability in git clone command.

The URL and destination path are passed directly to a shell command using string interpolation with double quotes. If either parameter contains special characters or malicious input (e.g., "; rm -rf /"), this could lead to command injection.

Consider using Node's execFile or spawn with argument arrays instead of shell command strings, or at minimum, validate/sanitize inputs more rigorously.

Apply this approach for safer execution:

-			await execAsync(`git clone "${url}" "${destinationPath}"`, {
-				encoding: "utf-8",
-			});
+			const { spawn } = require("node:child_process");
+			await new Promise((resolve, reject) => {
+				const proc = spawn("git", ["clone", url, destinationPath]);
+				proc.on("close", (code) => {
+					if (code === 0) resolve(undefined);
+					else reject(new Error(`git clone failed with code ${code}`));
+				});
+			});

Alternatively, verify that the URL and destinationPath are properly validated/sanitized before use.


I'll verify the shell injection concerns by examining the actual code implementation and how execAsync handles arguments.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine the cloneRepository method in worktree-manager.ts
fd worktree-manager.ts --type f -x cat {} | head -1500 | tail -200

Length of output: 5074


🏁 Script executed:

#!/bin/bash
# Search for execAsync definition and usage patterns
rg -n "execAsync" -B 3 -A 8 --type ts

Length of output: 19534


🏁 Script executed:

#!/bin/bash
# Check how execAsync is imported and what library provides it
rg -n "import.*execAsync|from.*execAsync" --type ts -C 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for other instances where shell commands are executed with user input
rg -n "spawn|exec|execFile" -B 2 -A 5 --type ts | head -100

Length of output: 6081


🏁 Script executed:

#!/bin/bash
# Check if there are any shell injection prevention utilities in the codebase
rg -n "escape|sanitize|quote" --type ts | head -50

Length of output: 1470


🏁 Script executed:

#!/bin/bash
# Examine the full cloneRepository method implementation
fd worktree-manager.ts --type f -x grep -n "cloneRepository" -A 60 {}

Length of output: 1676



---

🌐 Web query:

```web
Node.js child_process exec shell injection vulnerability double quotes

💡 Result:

Short answer: wrapping untrusted input in double quotes when using child_process.exec is not a safe protection against shell injection. exec spawns a shell and shell metacharacters (e.g. ; & | && || $() ... redirections, etc.) can still be interpreted or can break quoting; there have also been real-world failures where libraries meant to quote/escape input allowed metacharacters through. [1][2][3]

What to do instead (concise):

  • Avoid exec. Use execFile or spawn and pass arguments as an array (no shell interpretation). Example: child_process.spawn('ping', ['-c','1', destination]) or execFile('prog', [arg1, arg2]). This is the recommended safe pattern. [1][2]
  • If you must run a shell, either whitelist/validate allowed values strictly, or use a proven shell-escaping library and keep it up-to-date (beware past vulnerabilities such as shell-quote CVE-2021-42740). Even then prefer avoiding a shell. [2][3]
  • On Windows be careful: .bat/.cmd semantics require a shell; still validate/whitelist inputs there. [1]

Sources:

  • Node.js child_process docs (exec vs execFile/spawn, warning about unsanitized input). [1]
  • Practical guidance / OWASP-style writeups on Node command-injection prevention (advocates spawn/execFile and warns that quoting isn't sufficient). [2]
  • CVE-2021-42740 (shell-quote command-injection example showing how quoting/escaping tooling can fail). [3]

🌐 Web query:

Node.js exec vs spawn security difference

💡 Result:

Short answer: exec is riskier. exec runs a shell (via /bin/sh -c) and interprets metacharacters, so passing untrusted strings to it allows command injection. spawn and execFile invoke a program directly with an argument array (no shell) by default, which prevents shell metacharacter injection. However spawn/execFile can still be unsafe if you enable shell:true, pass unsanitized args, leak env vars, or run with excessive privileges — so always validate/whitelist input, prefer argument arrays, drop privileges, and avoid shells when possible. [1][2][3][4][5]

Key points and recommendations

  • Why exec is dangerous: it spawns a shell and concatenates the command string, so things like ;, &&, |, $(), backticks let an attacker run extra commands. Use only with fully trusted/static commands. [1][2][5]
  • Why spawn/execFile are safer: they call execve on the target program and accept an args array, so user input is passed as a single argument (no shell parsing). Prefer these when dealing with untrusted input. [1][3][4]
  • Caveats:
    • spawn(..., { shell: true }) or execFile with shell enabled reintroduces shell parsing and the risks of exec. [1][3]
    • exec buffers entire output (maxBuffer), which can cause other issues; spawn streams stdout/stderr. [4]
    • Environment variables and working directory can leak secrets—set a safe env and cwd. [3]
    • Always validate/whitelist inputs, escape or avoid calling the shell, and run with least privilege. [2][3][5]

Sources: [1][2][3][4][5]

References
[1] SecureFlag / OS Command Injection in NodeJS.
[2] Built In — Node.js app security (child_process guidance).
[3] NodeJS security blog — avoid shell, use execFile/spawn and sanitize.
[4] Stack Overflow — technical difference: exec uses shell vs spawn execve.
[5] Datadog static-analysis guidance on child_process.exec risks.


🌐 Web query:

Can command substitution happen inside double quotes shell

💡 Result:

Yes. Command substitution ( $(...) or ... ) is recognized and performed when it appears inside double quotes. The substitution's output then replaces the $(...) text; because it was inside double quotes, the shell does NOT perform word-splitting or pathname (globbing) on that output. Also, a command substitution begins its own quoting context, so quotes inside the substitution don't pair with quotes outside. Examples and standards: Bash manual and the POSIX shell spec. [1][2][3]

Example:

  • echo "$(printf 'a b')"
    prints: a b
  • but the result won't be split into separate words by the shell because it was double-quoted. [1][2]

Sources:
[1] Bash Reference Manual — Command Substitution.
[2] POSIX Shell Command Language — Command Substitution.
[3] Discussion / examples (Unix & Linux / StackOverflow).


Shell injection vulnerability confirmed in cloneRepository method.

The use of exec() with double-quoted parameters allows command substitution and shell metacharacters to be interpreted, bypassing the URL validation. A malicious URL like "https://github.com/user/repo.git$(malicious-command)" would execute arbitrary code.

The suggested fix using spawn() with an argument array is the correct approach. spawn and execFile invoke a program directly with an argument array (no shell) by default, which prevents shell metacharacter injection.

Apply the safer pattern shown in the original review comment or use similar approach for destinationPath validation.

🤖 Prompt for AI Agents
apps/desktop/src/main/lib/worktree-manager.ts around lines 1335-1388: the
current implementation uses execAsync with a single shell command string which
allows shell metacharacter/command-substitution injection via a crafted URL or
path; replace this with a non-shell child process invocation
(child_process.spawn or execFile) that passes the git command and its arguments
as an array (e.g., ['clone', url, destinationPath]) so the shell is not used,
validate/sanitize destinationPath (no shell metacharacters, path traversal, and
ensure it is a valid filesystem path), run the process and await its exit via a
Promise that resolves on exit code 0 and rejects on non-zero code while
collecting stderr/stdout for diagnostics, and propagate a safe, extracted error
message on failure.

Resolved merge conflicts by accepting main branch changes:
- src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts
- src/renderer/screens/main/components/Layout/TaskTabs/TaskTabs.tsx
- src/renderer/screens/main/components/Layout/TaskTabs/WorktreeTabButton.tsx
- src/renderer/screens/main/hooks/useTasks.ts
- src/renderer/screens/main/utils.ts
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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (2)

20-24: Memoize recentWorkspaces to prevent unnecessary effect re-runs.

The recentWorkspaces array is recalculated on every render, creating a new reference even when the underlying data hasn't changed. This causes the keyboard navigation effect (line 38) to re-run unnecessarily.

Apply this diff to memoize the array:

+import { useEffect, useMemo, useState } from "react";
-import { useEffect, useState } from "react";
-	// Show recent workspaces (limit to 5 for display)
-	const recentWorkspaces = workspaces.slice(0, 5);
+	// Show recent workspaces (limit to 5 for display)
+	const recentWorkspaces = useMemo(() => workspaces.slice(0, 5), [workspaces]);

189-191: Consider using a named constant for the button offset.

The magic number 2 (line 190) represents the number of action buttons before the recent workspaces list. While the comment explains this, using a named constant would make the code more maintainable and reduce the risk of errors if buttons are added or removed.

Apply this diff:

+	// Number of action buttons before the recent workspaces list
+	const ACTION_BUTTON_COUNT = 2;
+
 	// Total navigable items: 2 action buttons + recent workspaces
 	// (excluding disabled SSH button)
-	const totalItems = 2 + recentWorkspaces.length;
+	const totalItems = ACTION_BUTTON_COUNT + recentWorkspaces.length;
 		{recentWorkspaces.map((workspace, index) => {
-			const itemIndex = index + 2; // Offset by 2 for the action buttons
+			const itemIndex = index + ACTION_BUTTON_COUNT;
 			return (
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac26a9d and ff8388f.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/globals.css (1 hunks)
  • apps/desktop/src/renderer/index.html (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/CloneFromUrlDialog.tsx (1)
  • CloneFromUrlDialog (21-184)
🔇 Additional comments (3)
apps/desktop/src/renderer/index.html (1)

14-17: Google Fonts integration follows best practices and the .font-micro5 class is actively used.

The preconnect optimization and display=swap parameter ensure efficient font loading and prevent layout shifts. The class is defined in globals.css:139 and actively used in WorkspaceSelectionModal.tsx:124. CSP policy already permits external resources.

apps/desktop/src/renderer/globals.css (1)

138-141: The AI summary was inaccurate; no duplication exists.

Verification shows .font-micro5 is defined only once in the file (line 139). There is no duplicate definition at top-level or elsewhere. The single definition is correct and requires no changes.

Likely an incorrect or invalid review comment.

apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx (1)

90-113: No changes needed. IPC contract is properly typed and validated.

The IPC channel "workspace-clone-from-url" is properly defined with response type IpcResponse<Workspace> in the shared types. The Workspace interface guarantees an id: string field as required, so when result.data passes the truthiness check at line 100, it is guaranteed to have an id property. The existing validation (result.success && result.data) already provides sufficient type-safe protection through TypeScript's type system. The suggested additional check for result.data.id is unnecessary.

Comment on lines +38 to +64
useEffect(() => {
if (!isOpen || showCloneDialog) return;

const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case "ArrowDown":
case "ArrowRight":
e.preventDefault();
setSelectedIndex((prev) =>
prev < totalItems - 1 ? prev + 1 : prev,
);
break;
case "ArrowUp":
case "ArrowLeft":
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
break;
case "Enter":
e.preventDefault();
handleSelectItem(selectedIndex);
break;
}
};

window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isOpen, showCloneDialog, selectedIndex, totalItems, recentWorkspaces]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Include handleSelectItem in the effect's dependency array.

The keyboard navigation effect calls handleSelectItem (line 57) but doesn't include it in the dependency array (line 64). This creates a stale closure risk where the effect might reference an outdated version of the function.

Apply this diff:

 		window.addEventListener("keydown", handleKeyDown);
 		return () => window.removeEventListener("keydown", handleKeyDown);
-	}, [isOpen, showCloneDialog, selectedIndex, totalItems, recentWorkspaces]);
+	}, [isOpen, showCloneDialog, selectedIndex, totalItems, recentWorkspaces, handleSelectItem]);

Note: If ESLint warnings about exhaustive-deps appear after this change, consider wrapping handleSelectItem in useCallback to stabilize its reference.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!isOpen || showCloneDialog) return;
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case "ArrowDown":
case "ArrowRight":
e.preventDefault();
setSelectedIndex((prev) =>
prev < totalItems - 1 ? prev + 1 : prev,
);
break;
case "ArrowUp":
case "ArrowLeft":
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
break;
case "Enter":
e.preventDefault();
handleSelectItem(selectedIndex);
break;
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isOpen, showCloneDialog, selectedIndex, totalItems, recentWorkspaces]);
useEffect(() => {
if (!isOpen || showCloneDialog) return;
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case "ArrowDown":
case "ArrowRight":
e.preventDefault();
setSelectedIndex((prev) =>
prev < totalItems - 1 ? prev + 1 : prev,
);
break;
case "ArrowUp":
case "ArrowLeft":
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
break;
case "Enter":
e.preventDefault();
handleSelectItem(selectedIndex);
break;
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isOpen, showCloneDialog, selectedIndex, totalItems, recentWorkspaces, handleSelectItem]);
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx
around lines 38 to 64, the useEffect that registers the keyboard handler calls
handleSelectItem but does not list it in the dependency array, creating a
stale-closure risk; update the effect dependencies to include handleSelectItem,
and if ESLint flags exhaustive-deps or you want to avoid the handler
re-registering unnecessarily, wrap handleSelectItem in useCallback with stable
dependencies so the effect can safely depend on it.


return (
<Dialog open={isOpen} onOpenChange={() => { }}>
<Dialog open={isOpen} onOpenChange={() => {}}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dialog cannot be closed via overlay or ESC key.

The onOpenChange prop is set to an empty function, which prevents users from closing the modal via the overlay click or ESC key. Users must use one of the action buttons.

If this is intentional to enforce a specific user flow, consider adding an explicit close button or documenting this behavior. Otherwise, provide a proper close handler.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx
around line 116, the Dialog component's onOpenChange is currently an empty
function preventing closing via overlay or ESC; replace the no-op with a proper
handler that updates the modal open state (or calls the provided close
callback/prop) so overlay clicks and ESC close the dialog, or if intentional,
add an explicit close button and document the behavior; ensure the handler
toggles/sets isOpen false and performs any cleanup (e.g., resetting selection)
consistently.

Comment on lines +163 to +176
<button
type="button"
onClick={handleOpenProject}
className="group flex flex-col items-center justify-center w-full md:w-52 h-32 px-3 py-4 rounded-lg border border-neutral-700 bg-neutral-800/50 hover:bg-neutral-800 hover:border-neutral-600 transition-all opacity-50 cursor-not-allowed"
disabled
title="Coming soon"
>
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-neutral-700/50 mb-2">
<Terminal className="w-5 h-5 text-neutral-300" />
</div>
<div className="text-sm font-medium text-white">
Connect via SSH
</div>
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove or correct the onClick handler for the disabled SSH button.

The "Connect via SSH" button is disabled (line 167) but has onClick={handleOpenProject} (line 165), which is incorrect. Even though disabled buttons don't fire onClick events, having the wrong handler is confusing for maintainers.

Apply this diff:

 	<button
 		type="button"
-		onClick={handleOpenProject}
 		className="group flex flex-col items-center justify-center w-full md:w-52 h-32 px-3 py-4 rounded-lg border border-neutral-700 bg-neutral-800/50 hover:bg-neutral-800 hover:border-neutral-600 transition-all opacity-50 cursor-not-allowed"
 		disabled
 		title="Coming soon"
 	>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
onClick={handleOpenProject}
className="group flex flex-col items-center justify-center w-full md:w-52 h-32 px-3 py-4 rounded-lg border border-neutral-700 bg-neutral-800/50 hover:bg-neutral-800 hover:border-neutral-600 transition-all opacity-50 cursor-not-allowed"
disabled
title="Coming soon"
>
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-neutral-700/50 mb-2">
<Terminal className="w-5 h-5 text-neutral-300" />
</div>
<div className="text-sm font-medium text-white">
Connect via SSH
</div>
</button>
<button
type="button"
className="group flex flex-col items-center justify-center w-full md:w-52 h-32 px-3 py-4 rounded-lg border border-neutral-700 bg-neutral-800/50 hover:bg-neutral-800 hover:border-neutral-600 transition-all opacity-50 cursor-not-allowed"
disabled
title="Coming soon"
>
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-neutral-700/50 mb-2">
<Terminal className="w-5 h-5 text-neutral-300" />
</div>
<div className="text-sm font-medium text-white">
Connect via SSH
</div>
</button>
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceSelectionModal/WorkspaceSelectionModal.tsx
around lines 163 to 176, the disabled "Connect via SSH" button includes an
incorrect onClick={handleOpenProject} handler; remove the onClick prop (or set
it to undefined) so the disabled control has no click handler and update props
accordingly to avoid misleading maintainers, keeping the disabled and
title="Coming soon" as-is.

@Kitenite Kitenite closed this Dec 1, 2025
@Kitenite Kitenite deleted the basic-ui-polishing branch December 1, 2025 23:17
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.

2 participants