Skip to content

new terminal#79

Merged
Kitenite merged 5 commits intomainfrom
new-terminal
Nov 14, 2025
Merged

new terminal#79
Kitenite merged 5 commits intomainfrom
new-terminal

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Nov 14, 2025

  • new button
  • fix new modal button
  • create task

Summary by CodeRabbit

  • New Features

    • Add Task modal now fetches and lists tasks, supports a dedicated New Task form, and shows a Creating view with live progress/output and clear-status control.
    • New Tab button with dropdown to create Terminal or Preview tabs.
  • UI Improvements

    • Dialog gains controllable modal behavior.
    • Minor z-index adjustment for sidebar trigger.
  • Bug Fixes / Reliability

    • Improved task-creation progress handling, status clearing, and streaming output behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This PR refactors the AddTaskModal to fetch and manage tasks internally (removing tasks prop / MOCK_TASKS), extracts NewTabButton for tab creation, adds a Radix-based dropdown-menu UI, updates Dialog to accept a modal prop, and extends useTasks with a handleClearStatus API and safer progress handling.

Changes

Cohort / File(s) Summary
Task modal refactor
apps/desktop/src/renderer/screens/main/MainScreen.tsx, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx, apps/desktop/src/renderer/screens/main/components/Layout/NewLayoutMain.tsx, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/index.ts, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/types.ts
Removed passing of tasks/MOCK_TASKS. AddTaskModal redesigned to use internal hooks/components (TaskList, TaskForm, CreatingView) and new types; public props changed (removed tasks, added onClearStatus and apiBaseUrl). Exports consolidated via index.
Task data/form hooks & utils
apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskData.ts, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts
Added hooks to fetch/transform API tasks (useTaskData), manage creation form state/branch generation (useTaskForm), and utilities (transformAPITaskToUITask, generateBranchNameWithCollisionAvoidance, formatRelativeTime).
Task modal subcomponents
apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskForm.tsx, apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/CreatingView.tsx
New UI components extracted for list, form, and creation-progress views used by AddTaskModal.
useTasks enhancements
apps/desktop/src/renderer/screens/main/hooks/useTasks.ts
Added IPC progress handling, guarded progress listener lifecycle, exposed handleClearStatus(), and adjusted creation success/error/modal-close logic to respect modal open state and selection switching.
Worktree tab creation refactor
apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/WorktreeList.tsx, apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/NewTabButton.tsx, apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/index.ts
Extracted dropdown/tab-creation UI into new NewTabButton component; WorktreeList now renders NewTabButton and removed local dropdown logic. NewTabButton handles default type via localStorage and IPC tab-create.
UI library additions
packages/ui/src/components/dropdown-menu.tsx, packages/ui/src/components/dialog.tsx, packages/ui/package.json
Added a Radix-wrapped dropdown-menu component with many primitives and styling utilities, exported it in package.json, added @radix-ui/react-dropdown-menu dependency, and added modal?: boolean prop to Dialog component forwarded to Radix primitive.
Terminal output update logic
apps/desktop/src/renderer/screens/main/components/Sidebar/components/CreateWorktreeModal/TerminalOutput.tsx
Improved incremental update logic to append only deltas, skip identical updates, and handle full rewrites safely.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant MainScreen
    participant AddTaskModal
    participant useTaskData
    participant API
    participant useTasks

    User->>MainScreen: Click "Add Task"
    MainScreen->>AddTaskModal: open(isOpen=true)
    AddTaskModal->>useTaskData: fetch tasks (mode=list, apiBaseUrl)
    useTaskData->>API: GET /tasks
    API-->>useTaskData: APITask[]
    useTaskData->>AddTaskModal: tasks, isLoadingTasks, tasksError
    User->>AddTaskModal: select task OR switch to "New"
    AddTaskModal->>useTasks: handleCreateTask(taskData)
    useTasks->>useTasks: attach progress listener, create via IPC/API
    useTasks-->>AddTaskModal: setupStatus / setupOutput updates
    User->>AddTaskModal: Click Clear
    AddTaskModal->>useTasks: onClearStatus()
    useTasks-->>useTasks: reset setupStatus & setupOutput
Loading
sequenceDiagram
    actor User
    participant WorktreeList
    participant NewTabButton
    participant LocalStorage
    participant IPC

    WorktreeList->>NewTabButton: render(currentWorkspace, selectedWorktreeId)
    NewTabButton->>LocalStorage: read "newTabDefaultType"
    LocalStorage-->>NewTabButton: defaultType
    User->>NewTabButton: click main button or dropdown item
    NewTabButton->>LocalStorage: write "newTabDefaultType" (if changed)
    NewTabButton->>IPC: ipcRenderer("tab-create", {type, worktreeId})
    IPC-->>NewTabButton: newTabId
    NewTabButton->>WorktreeList: onTabSelect(newTabId), onReload()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • useTasks: progress listener lifecycle, race conditions, and modal-open checks.
    • useTaskData/useTaskForm: API response parsing, cancellation handling, and branch-generation logic.
    • NewTabButton: IPC error handling and localStorage sync.
    • dropdown-menu export and Dialog modal prop for API/backwards compatibility.

Possibly related PRs

  • workspace #71 — touches useTasks and task/modal integration; likely overlaps progress/creation logic changes.
  • Tabs creation and management #78 — modifies AddTaskModal props and useTasks API; strong overlap in prop/signature changes.
  • Plan view feature #37 — previously introduced MOCK_TASKS and task prop usage that this PR removes/refactors.

Poem

🐰 I nibble code beneath the moonlit keys,

Tasks now fetched from distant API trees,
Tabs pop open with a dropdown's cheer,
Status cleared — progress loud and clear,
A rabbit hops — your UI springs to please 🌿

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete and lacks required template sections. Only vague bullet points are provided with no detailed explanation, related issues, type of change, testing info, or additional context. Complete the PR description template with detailed changes, related issues, type of change selection, testing steps, and any relevant screenshots or notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'new terminal' is vague and overly generic. While the PR includes terminal/tab creation features, the changes are far broader, encompassing task management, modal refactoring, API integration, and UI component restructuring. Use a more descriptive title that captures the main scope, such as 'Refactor task modal with API integration and new tab button component' or 'Add NewTabButton component and task API integration'.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6d45c2 and d891e87.

📒 Files selected for processing (11)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx (7 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 (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/TaskList.tsx (1 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 (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/useTaskForm.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal/utils.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/CreateWorktreeModal/TerminalOutput.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useTasks.ts (7 hunks)

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: 4

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

49-95: API integration looks good, but consider making the API URL configurable.

The new APITask interface and prop changes align well with the shift to API-driven task loading. However, the hardcoded default apiBaseUrl = "http://localhost:3000" may cause issues in different environments.

Consider:

  1. Loading the API base URL from environment variables or configuration
  2. Documenting the expected API endpoint format for future maintainers
 export const AddTaskModal: React.FC<AddTaskModalProps> = ({
 	isOpen,
 	onClose,
 	openTasks,
 	onSelectTask,
 	onCreateTask,
 	initialMode = "list",
 	branches = [],
 	worktrees = [],
 	isCreating = false,
 	setupStatus,
 	setupOutput,
 	onClearStatus,
-	apiBaseUrl = "http://localhost:3000",
+	apiBaseUrl = process.env.REACT_APP_API_URL || "http://localhost:3000",
 }) => {

381-390: Consider user control over auto-transition behavior.

The automatic transition back to list mode after 1.5 seconds provides a smooth UX, but some users might want to review the output longer before transitioning.

Consider one of these alternatives:

  1. Show a manual "Done" or "Back to List" button instead of auto-transitioning
  2. Increase the delay to 2-3 seconds for better readability
  3. Add a user preference for auto-transition behavior

The current implementation with cleanup is technically correct.

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

65-86: Dropdown-menu wrapper API and styling look solid

The rest of the wrappers (Group, Item, CheckboxItem, RadioGroup, RadioItem, Label, Separator, Shortcut, Sub, SubTrigger, SubContent) are idiomatic:

  • Correctly forward props to Radix primitives.
  • Expose extra props like inset and variant via data-* attributes for styling.
  • Provide consistent data-slot hooks and Tailwind classes that match the rest of the UI kit.

No functional issues stand out here.

If you find yourself repeating similar Tailwind strings across multiple components, a small shared dropdownItemBaseClass constant could reduce duplication, but that’s purely optional.

Also applies to: 88-147, 149-242

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

1-32: State initialization and props for NewTabButton look good; consider guarding localStorage

The lazy useState initializer for defaultTabType correctly reads from localStorage and falls back to "terminal", and the props match how WorktreeList passes currentWorkspace, selectedWorktreeId, onTabSelect, and onReload. This is a clean separation of concerns.

In environments where localStorage might not be available (e.g., certain test runners, non-browser tooling), this could throw. If you expect to render this component in such contexts, a small defensive tweak would make it more robust:

-const [defaultTabType, setDefaultTabType] = useState<"terminal" | "preview">(
-  () => {
-    // Load from localStorage or default to "terminal"
-    const saved = localStorage.getItem("newTabDefaultType");
-    return (saved === "preview" ? "preview" : "terminal") as
-      | "terminal"
-      | "preview";
-  },
-);
+const [defaultTabType, setDefaultTabType] = useState<"terminal" | "preview">(
+  () => {
+    try {
+      const saved =
+        typeof window !== "undefined"
+          ? window.localStorage.getItem("newTabDefaultType")
+          : null;
+      return saved === "preview" ? "preview" : "terminal";
+    } catch {
+      return "terminal";
+    }
+  },
+);

Totally optional if this component only ever runs in an Electron 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 b6157d4 and a6d45c2.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • apps/desktop/src/renderer/screens/main/MainScreen.tsx (3 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx (10 hunks)
  • apps/desktop/src/renderer/screens/main/components/Layout/NewLayoutMain.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/WorktreeList.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/NewTabButton.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/hooks/useTasks.ts (3 hunks)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/dialog.tsx (1 hunks)
  • packages/ui/src/components/dropdown-menu.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/screens/main/components/Layout/NewLayoutMain.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/WorktreeList.tsx (1)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/NewTabButton.tsx (1)
  • NewTabButton (18-152)
apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx (4)
apps/desktop/src/renderer/screens/main/components/Layout/StatusIndicator.tsx (1)
  • TaskStatus (3-11)
packages/db/src/schema/schema.ts (1)
  • tasks (109-148)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/CreateWorktreeModal/TerminalOutput.tsx (1)
  • TerminalOutput (11-84)
apps/desktop/src/renderer/screens/main/components/Layout/Avatar.tsx (1)
  • Avatar (9-48)
apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/NewTabButton.tsx (1)
apps/desktop/src/shared/types.ts (1)
  • Workspace (70-82)
🪛 GitHub Actions: CI
packages/ui/package.json

[error] 1-1: lint:check failed due to multiple errors; see individual diagnostics. (Command failed with exit code 1)

packages/ui/src/components/dropdown-menu.tsx

[warning] 6-6: Formatter would have modified formatting in DropdownMenu.tsx (prettier formatting).

🔇 Additional comments (15)
apps/desktop/src/renderer/screens/main/MainScreen.tsx (2)

105-105: LGTM! Clean integration of status clearing callback.

The handleClearStatus callback is correctly wired from useTasks to AddTaskModal, enabling external reset of setup state.

Also applies to: 216-216


121-121: No issues identified with the z-index change.

The reduction from z-50 to z-30 is a deliberate layering strategy. The trigger element is a narrow 2px hover strip on the left edge that only renders when the sidebar is hidden. At z-30, it sits below modals and dialogs (z-50), preventing it from interfering with critical UI elements. The sidebar overlay remains at z-40, positioned above the trigger, allowing the hover-to-reveal interaction to work correctly. This stacking order is sound and consistent with the component's purpose.

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

62-65: LGTM! Clean utility function for status reset.

The handleClearStatus function provides a simple way to reset setup state and is correctly exposed in the hook's API.

Also applies to: 244-244


191-193: LGTM! Appropriate fallback for success message.

The logic correctly displays the detailed setup result when available and falls back to a generic success message otherwise.

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

97-125: LGTM! Clean helper functions for data transformation.

Both formatRelativeTime and transformAPITaskToUITask are well-implemented and handle null values appropriately.


564-590: LGTM! Clear loading and error state handling.

The conditional rendering provides good user feedback for loading, error, empty, and filtered states.


465-467: LGTM! Clear comment explaining intentional behavior.

The comment helpfully documents that the form is intentionally kept visible during creation to show progress, unlike the previous behavior.

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

1-2: Barrel export for NewTabButton looks good

The re-export cleanly exposes NewTabButton via the components/NewTabButton directory and matches how it’s imported in WorktreeList.

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

3-4: Confirm NewTabButton visibility gating on selectedWorktreeId

NewTabButton is only rendered when selectedWorktreeId is truthy:

{selectedWorktreeId && currentWorkspace && (
  <NewTabButton  />
)}

Given selectedWorktreeId is string | null | undefined, this means:

  • No “New Tab” button will appear when there’s no selected worktree.
  • Previously, the new-tab affordance may have been visible regardless of selection (depending on the old implementation).

If the UX is that you can only create new tabs for an explicitly selected worktree, this is fine. If users should always be able to create a tab for the active worktree even when selectedWorktreeId is unset, you might want to fall back to currentWorkspace.activeWorktreeId here.

Also applies to: 76-84

apps/desktop/src/renderer/screens/main/components/Sidebar/components/WorktreeList/components/NewTabButton/NewTabButton.tsx (2)

34-95: IPC-based tab creation handlers are correct; ensure window.ipcRenderer exists in all render contexts

handleAddTerminal and handleAddPreview both:

  • Guard on currentWorkspace and selectedWorktreeId.
  • Invoke "tab-create" via window.ipcRenderer.invoke.
  • On success, call onTabSelect(selectedWorktreeId, newTabId) and then onReload.
  • Catch and log errors.

This is a solid flow for the desktop renderer. Just be aware that window.ipcRenderer is Electron-specific; if this component is ever rendered in Storybook, tests, or other non-Electron browser contexts, clicking the button will throw unless you stub window.ipcRenderer or guard against its absence:

if (!("ipcRenderer" in window)) {
  console.error("ipcRenderer not available");
  return;
}

Whether you add such a guard or rely on test setup stubs is a project-level choice.


97-151: New Tab button + dropdown UX is clean and matches the design intent

The “Arc-style” main button plus chevron dropdown is implemented cleanly:

  • handleCreateDefault respects the stored default tab type.
  • The dropdown uses DropdownMenu + DropdownMenuTrigger asChild correctly.
  • onMouseDown={(e) => e.stopPropagation()} on the chevron button prevents accidental triggering of the main button while still letting the dropdown open.
  • Menu items correctly update the default type when selected and then create the respective tab.

No functional issues here.

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

39-55: The Tailwind syntax is valid—no changes needed.

Tailwind supports the parentheses shorthand for CSS variables (e.g., max-h-(--my-var)), which is equivalent to using var(...) inside square-bracket arbitrary values. The classes max-h-(--radix-dropdown-menu-content-available-height) and origin-(--radix-dropdown-menu-content-transform-origin) will compile correctly as-is.


1-55: Update the formatter reference and correction command

The project uses Biome for formatting (not Prettier), configured in the root package.json. The correct command is bun format from the repository root. Please run this to normalize the file before merging to ensure it matches repository style and keeps CI green.


7-18: Confirm the intentional modal = false default override

The wrapper defaults modal to false, but Radix's DropdownMenu.Root v2.1.16 documents the modal prop default as true. This means your wrapper deliberately inverts the Radix default. When modal is enabled, focus is trapped to the open menu and closing restores focus to the trigger.

Since this is a new wrapper component with no backward-compat constraints, this design choice is valid if intentional—you may want all dropdowns in this package to be non-modal by default. Confirm this is the desired behavior for your UI library, otherwise consider removing the default and deferring to Radix's modal behavior.

packages/ui/package.json (1)

17-18: Export and dependency additions are correctly wired—verify lint status locally

The new export:

"./dropdown-menu": "./src/components/dropdown-menu.tsx"

and dependency:

"@radix-ui/react-dropdown-menu": "^2.1.16"

are correctly formatted, syntactically valid, and follow the established pattern for other Radix UI components. The component file exists at the expected path.

However, lint failures reported by CI cannot be verified in this environment. Run bun run lint:check locally to confirm whether failures remain and whether they're related to these changes or pre-existing issues.

Comment thread apps/desktop/src/renderer/screens/main/components/Layout/AddTaskModal.tsx Outdated
Comment on lines +633 to +710
{(isCreating || setupStatus) ? (
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
<ScrollArea className="flex-1 min-h-0">
<div className="px-6 pt-6 pb-4">
{/* Progress Section - shown while creating */}
{isCreating && (
<div className="flex-1 flex flex-col space-y-3 overflow-hidden min-h-[200px]">
<div className="flex items-center gap-2 text-sm text-neutral-300">
<Loader2 size={16} className="animate-spin" />
<span>{setupStatus || "Creating worktree..."}</span>
</div>

{setupOutput && (
<div className="bg-neutral-900 rounded border border-neutral-700 overflow-hidden min-h-[200px]">
<TerminalOutput
output={setupOutput}
className="w-full h-full"
/>
{setupOutput && (
<div className="flex-1 bg-neutral-900 rounded border border-neutral-700 overflow-hidden min-h-[400px]">
<TerminalOutput
output={setupOutput}
className="w-full h-full"
/>
</div>
)}
</div>
)}
</div>
)}

{/* Error Display - shown when creation failed */}
{!isCreating &&
setupStatus &&
(setupStatus.toLowerCase().includes("failed") ||
setupStatus.toLowerCase().includes("error")) && (
<div className="flex flex-col space-y-3 min-h-[200px] pt-4">
<div className="flex items-center gap-2 text-sm text-red-400 font-medium">
<span>{setupStatus}</span>
</div>

{setupOutput && (
<div className="bg-red-500/10 rounded border border-red-500/30 p-3 overflow-auto min-h-[200px]">
<pre className="text-red-200 text-xs font-mono whitespace-pre-wrap">
{setupOutput}
</pre>
{/* Success Display - shown when creation succeeded */}
{!isCreating &&
setupStatus &&
!setupStatus.toLowerCase().includes("failed") &&
!setupStatus.toLowerCase().includes("error") &&
(setupStatus.toLowerCase().includes("success") ||
setupStatus.toLowerCase().includes("completed")) && (
<div className="flex-1 flex flex-col space-y-3 overflow-hidden min-h-[200px]">
<div className="flex items-center gap-2 text-sm text-green-400 font-medium">
<span>{setupStatus}</span>
</div>

{setupOutput && (
<div className="flex-1 bg-green-500/10 rounded border border-green-500/30 p-3 overflow-auto min-h-[400px]">
<pre className="text-green-200 text-xs font-mono whitespace-pre-wrap">
{setupOutput}
</pre>
</div>
)}
</div>
)}
</div>
)}

{/* Worktree creation options */}
{(branches.length > 0 || worktrees.length > 0) && (
<div className="space-y-3 pt-4">
{branches.length > 0 && (
{/* Error Display - shown when creation failed */}
{!isCreating &&
setupStatus &&
(setupStatus.toLowerCase().includes("failed") ||
setupStatus.toLowerCase().includes("error")) && (
<div className="flex-1 flex flex-col space-y-3 overflow-hidden min-h-[200px]">
<div className="flex items-center gap-2 text-sm text-red-400 font-medium">
<span>{setupStatus}</span>
</div>

{setupOutput && (
<div className="flex-1 bg-red-500/10 rounded border border-red-500/30 p-3 overflow-auto min-h-[400px]">
<pre className="text-red-200 text-xs font-mono whitespace-pre-wrap">
{setupOutput}
</pre>
</div>
)}
</div>
)}
</div>
</ScrollArea>
{/* Footer for progress view */}
<div className="px-6 py-4 border-t border-neutral-800 flex items-center justify-end gap-2 shrink-0">
<Button
type="button"
variant="ghost"
onClick={onClose}
disabled={isCreating}
>
{isCreating ? "Creating..." : "Close"}
</Button>
</div>
</div>
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.

🛠️ Refactor suggestion | 🟠 Major

Complex nested conditionals reduce readability.

The deeply nested conditions and string matching for status detection make this code harder to maintain and test.

Refactor to computed status and extracted components:

// Add a computed status type
type CreationStatus = 'idle' | 'creating' | 'success' | 'error';

// Add a helper function before the component
function getCreationStatus(
  isCreating: boolean, 
  setupStatus?: string
): CreationStatus {
  if (isCreating) return 'creating';
  if (!setupStatus) return 'idle';
  
  const status = setupStatus.toLowerCase();
  if (status.includes('success') || status.includes('completed')) {
    return 'success';
  }
  if (status.includes('failed') || status.includes('error')) {
    return 'error';
  }
  
  return 'idle';
}

// In the component:
const creationStatus = getCreationStatus(isCreating, setupStatus);

// Simplified rendering:
{creationStatus !== 'idle' ? (
  <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
    <ScrollArea className="flex-1 min-h-0">
      <div className="px-6 pt-6 pb-4">
        {creationStatus === 'creating' && (
          <CreatingView status={setupStatus} output={setupOutput} />
        )}
        {creationStatus === 'success' && (
          <SuccessView status={setupStatus} output={setupOutput} />
        )}
        {creationStatus === 'error' && (
          <ErrorView status={setupStatus} output={setupOutput} />
        )}
      </div>
    </ScrollArea>
    <ProgressFooter 
      isCreating={isCreating} 
      onClose={onClose}
    />
  </div>
) : (
  // Form fields
)}

This approach:

  • Eliminates string matching throughout the component
  • Makes testing easier (test the status function separately)
  • Improves readability with named status constants
  • Enables component extraction for each view

Comment on lines +200 to 214
// Only close modal and select worktree if modal is still open
if (isAddTaskModalOpen) {
// Close modal and reset state
setIsAddTaskModalOpen(false);
setSetupStatus(undefined);
setSetupOutput(undefined);

// Switch to the new worktree if available
if (result.worktree) {
setSelectedWorktreeId(result.worktree.id);
if (result.worktree.tabs && result.worktree.tabs.length > 0) {
handleTabSelect(result.worktree.id, result.worktree.tabs[0].id);
}
}
}
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 | 🔴 Critical

Critical: Stale closure bug with isAddTaskModalOpen state.

The isAddTaskModalOpen value is captured when the async function starts (Line 140), but by the time this code executes after await handleWorktreeCreated(), the actual modal state may have changed. React state values in closures don't update.

Scenario: If the user closes the modal while creation is in progress, the stale isAddTaskModalOpen value will still be true, causing Lines 203-213 to execute incorrectly.

Solution: Use a ref to track the latest modal state:

 export function useTasks({
 	currentWorkspace,
 	setSelectedWorktreeId,
 	handleTabSelect,
 	handleWorktreeCreated,
 }: UseTasksProps) {
 	const [isAddTaskModalOpen, setIsAddTaskModalOpen] = useState(false);
+	const isAddTaskModalOpenRef = useRef(false);
 	const [addTaskModalInitialMode, setAddTaskModalInitialMode] = useState<"list" | "new">("list");
 	// ... other state

+	// Keep ref in sync with state
+	useEffect(() => {
+		isAddTaskModalOpenRef.current = isAddTaskModalOpen;
+	}, [isAddTaskModalOpen]);

 	const handleOpenAddTaskModal = (mode: "list" | "new" = "list") => {
 		setAddTaskModalInitialMode(mode);
 		setIsAddTaskModalOpen(true);
+		isAddTaskModalOpenRef.current = true;
 		// ...
 	};

 	const handleCloseAddTaskModal = () => {
 		if (isCreatingWorktree) return;
 		setIsAddTaskModalOpen(false);
+		isAddTaskModalOpenRef.current = false;
 		// ...
 	};

 	// In handleCreateTask, use the ref:
-				if (isAddTaskModalOpen) {
+				if (isAddTaskModalOpenRef.current) {

Alternatively, consider moving the modal-close and worktree-selection logic outside this async function, triggered by status changes via useEffect.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 7 to 12
function Dialog({
modal = false,
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
return <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />;
}
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

Dialog’s new modal = false default may change existing behavior

By destructuring modal = false and always passing it to DialogPrimitive.Root, any existing <Dialog> usage that didn’t specify modal will now explicitly render with modal={false} instead of Radix’s default behavior (previously you were effectively passing modal={undefined}). If Radix’s default is modal (which it is for several primitives, including dropdown menu roots (lightrun.com)), this flips all existing dialogs to non‑modal: background remains interactive, focus may no longer be trapped, and body scroll/pointer‑event handling will change.

If that global behavior change isn’t intentional, consider either:

  • Dropping the default and just forwarding the prop:
-function Dialog({
-  modal = false,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Root>) {
-  return <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />;
-}
+function Dialog({
+  modal,
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Root>) {
+  return <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />;
+}
  • Or, if you do want a non‑modal default only in specific places, keep Radix’s default here and pass modal={false} explicitly at those call sites.

🌐 Web query:

What is the documented default value and behavior of the `modal` prop on `@radix-ui/react-dialog`’s `Dialog.Root`?

💡 Result:

Default: true — Dialog.Root’s modal prop defaults to true. When modal is true the dialog is modal: focus is trapped inside, interaction with the rest of the page is blocked (overlay/inert outside), body scroll is prevented, and typical modal behaviors (e.g. Esc to close, ARIA handling via Title/Description) are applied. [1]

Sources:
[1] Radix Primitives — Dialog (API Reference: Root → modal).


Dialog's new modal = false default changes existing behavior; requires fix

Radix UI's Dialog.Root component defaults to modal={true}, where focus is trapped, page interaction is blocked, body scroll is prevented, and modal behaviors are applied. By destructuring modal = false and always passing it to DialogPrimitive.Root, all existing <Dialog> usage without an explicit modal prop will now render non-modal—background remains interactive, focus is no longer trapped, and body scroll is enabled.

Fix by either:

  • Dropping the default and forwarding the prop directly (let Radix's default apply):
-function Dialog({
-  modal = false,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Root>) {
-  return <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />;
-}
+function Dialog({
+  modal,
+  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Root>) {
+  return <DialogPrimitive.Root data-slot="dialog" {...props} />;
+}
  • Or explicitly pass modal={false} only at call sites that require non-modal behavior.
📝 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
function Dialog({
modal = false,
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
return <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />;
}
function Dialog({
modal,
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
🤖 Prompt for AI Agents
In packages/ui/src/components/dialog.tsx around lines 7 to 12, the component
currently sets modal = false by default and always forwards modal to
DialogPrimitive.Root which overrides Radix's default modal behavior; remove the
default assignment and forward modal only when provided (or simply omit modal
from the props spread) so Radix's default (modal=true) remains in effect, or
alternatively delete the modal prop here and only pass modal={false} at explicit
call sites that require non-modal behavior.

@Kitenite Kitenite merged commit 4dbad17 into main Nov 14, 2025
1 of 5 checks passed
@Kitenite Kitenite deleted the new-terminal branch November 14, 2025 20:56
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