Skip to content

Commit 07b932c

Browse files
feat: add directory prompt on run and cloud mode gating (#132)
## Summary Implements the frictionless directory selection flow: when clicking "Run" without a directory set, Array prompts the user to either select an existing directory or clone the repository. ## User Flow ### For tasks WITH `repository_config`: When user clicks "Run (Local)" without a directory: 1. **Prompt**: "Do you have `org/repo` locally?" 2. **Options**: - **"I have it locally"** → Opens native folder picker → Saves mapping → Runs task - **"Clone for me"** → Triggers clone with progress UI → Saves mapping → User clicks Run again after clone - **"Cancel"** → Returns to task detail ### For tasks WITHOUT `repository_config`: When user clicks "Run (Local)" without a directory: 1. **Prompt**: "Select a working directory for this task" 2. **Opens folder picker** → Saves mapping → Runs task ### Invalid Directory Handling: - If user selects an invalid directory → Validation fails silently → Prompt appears again - No error spam, seamless recovery - User can clear directory with X button and retry ### Working Directory Field: - Shows **actual** directory that will be used (not derived fallback) - Empty with "Not set - click Run to select" when no directory - Clear button (X) appears only when directory is set ### Cloud Mode Gating: - Cloud mode toggle is **disabled** for tasks without `repository_config` - Tooltip explains: "Cloud mode requires a connected repository" ## Benefits 1. **Frictionless onboarding**: No upfront workspace configuration required 2. **Supports existing workflows**: Engineers can point to repos they already have cloned 3. **On-demand cloning**: Clone only when needed, with visual progress 4. **Flexible**: Supports both git-based and directory-only tasks 5. **Persistent**: Directory mappings survive app restarts 6. **Graceful recovery**: Invalid paths automatically re-prompt instead of erroring 7. **Clear UI**: Always shows what will actually be used ## Test Plan - [x] Task with repo, no directory set → Prompt appears with 3 buttons - [x] "I have it locally" → Folder picker opens → Selection persists - [x] "Clone for me" → Clone progress shows → Directory saved after clone - [x] Task without repo → Prompt shows "Select directory" with 2 buttons - [x] Cloud mode disabled when no `repository_config` → Tooltip shows - [x] Select invalid directory → Auto-prompts again (no error spam) - [x] Clear directory with X button → Working directory shows empty → Run shows prompt - [x] Working directory field shows actual path only --- **Stack**: Built on top of: - PR #128: Clone progress UI - PR #130: Task directory store foundation - PR #131: Integrate directory store into execution flow
1 parent 236fc37 commit 07b932c

File tree

4 files changed

+168
-168
lines changed

4 files changed

+168
-168
lines changed

src/renderer/features/task-detail/components/TaskActions.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface TaskActionsProps {
77
isCloningRepo: boolean;
88
cloneProgress: { message: string; percent: number } | null;
99
runMode: "local" | "cloud";
10+
hasRepositoryConfig: boolean;
1011
onRunTask: () => void;
1112
onCancel: () => void;
1213
onRunModeChange: (mode: "local" | "cloud") => void;
@@ -17,6 +18,7 @@ export const TaskActions: React.FC<TaskActionsProps> = ({
1718
isCloningRepo,
1819
cloneProgress,
1920
runMode,
21+
hasRepositoryConfig,
2022
onRunTask,
2123
onCancel,
2224
onRunModeChange,
@@ -58,12 +60,18 @@ export const TaskActions: React.FC<TaskActionsProps> = ({
5860
>
5961
<span className="truncate">{getRunButtonLabel()}</span>
6062
</Button>
61-
<Tooltip content="Toggle between Local or Cloud Agent">
63+
<Tooltip
64+
content={
65+
!hasRepositoryConfig
66+
? "Cloud mode requires a connected repository"
67+
: "Toggle between Local or Cloud Agent"
68+
}
69+
>
6270
<IconButton
6371
size="2"
6472
variant="classic"
6573
color={runMode === "cloud" ? "blue" : "gray"}
66-
disabled={isRunning || isCloningRepo}
74+
disabled={isRunning || isCloningRepo || !hasRepositoryConfig}
6775
onClick={() =>
6876
onRunModeChange(runMode === "local" ? "cloud" : "local")
6977
}

src/renderer/features/task-detail/components/TaskDetailPanel.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@ export function TaskDetailPanel({ taskId, task }: TaskDetailPanelProps) {
9090
? { status: execution.state.progress.status }
9191
: undefined
9292
}
93-
derivedPath={taskData.derivedPath}
94-
defaultWorkspace={taskData.defaultWorkspace}
9593
/>
9694
</Flex>
9795

@@ -101,6 +99,7 @@ export function TaskDetailPanel({ taskId, task }: TaskDetailPanelProps) {
10199
isCloningRepo={repository.isCloning}
102100
cloneProgress={taskData.cloneProgress}
103101
runMode={execution.state.runMode}
102+
hasRepositoryConfig={!!taskData.task.repository_config}
104103
onRunTask={execution.actions.run}
105104
onCancel={execution.actions.cancel}
106105
onRunModeChange={execution.actions.onRunModeChange}

src/renderer/features/task-detail/components/TaskMetadata.tsx

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
1-
import { Button, Code, DataList, Link, Text, Tooltip } from "@radix-ui/themes";
1+
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
2+
import { useTaskExecutionStore } from "@features/task-detail/stores/taskExecutionStore";
3+
import { Cross2Icon } from "@radix-ui/react-icons";
4+
import {
5+
Box,
6+
Button,
7+
Code,
8+
DataList,
9+
Flex,
10+
IconButton,
11+
Link,
12+
Text,
13+
Tooltip,
14+
} from "@radix-ui/themes";
215
import type { Task } from "@shared/types";
316
import { format, formatDistanceToNow } from "date-fns";
417
import type React from "react";
5-
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
6-
import { useTaskExecutionStore } from "@features/task-detail/stores/taskExecutionStore";
718

819
interface TaskMetadataProps {
920
task: Task;
1021
progress?: { status: string };
11-
derivedPath: string | null;
12-
defaultWorkspace: string | null;
1322
}
1423

1524
export const TaskMetadata: React.FC<TaskMetadataProps> = ({
1625
task,
1726
progress,
18-
derivedPath,
19-
defaultWorkspace,
2027
}) => {
21-
const { setRepoPath, revalidateRepo } = useTaskExecutionStore();
28+
const { setRepoPath, revalidateRepo, getTaskState } = useTaskExecutionStore();
29+
const taskState = getTaskState(task.id);
2230

2331
const handleWorkingDirectoryChange = async (newPath: string) => {
2432
setRepoPath(task.id, newPath);
2533
await revalidateRepo(task.id);
2634
};
2735

36+
const handleClearDirectory = () => {
37+
setRepoPath(task.id, null);
38+
};
39+
2840
return (
2941
<>
3042
<DataList.Root>
@@ -73,16 +85,28 @@ export const TaskMetadata: React.FC<TaskMetadataProps> = ({
7385
<DataList.Item>
7486
<DataList.Label>Working directory</DataList.Label>
7587
<DataList.Value>
76-
<FolderPicker
77-
value={derivedPath || ""}
78-
onChange={handleWorkingDirectoryChange}
79-
placeholder={
80-
!defaultWorkspace
81-
? "No workspace configured"
82-
: "Select working directory..."
83-
}
84-
size="2"
85-
/>
88+
<Flex gap="2" align="center" width="100%">
89+
<Box style={{ flex: 1, minWidth: 0 }}>
90+
<FolderPicker
91+
value={taskState.repoPath || ""}
92+
onChange={handleWorkingDirectoryChange}
93+
placeholder="Not set - click Run to select"
94+
size="2"
95+
/>
96+
</Box>
97+
{taskState.repoPath && (
98+
<Tooltip content="Clear directory selection">
99+
<IconButton
100+
size="2"
101+
variant="ghost"
102+
color="gray"
103+
onClick={handleClearDirectory}
104+
>
105+
<Cross2Icon />
106+
</IconButton>
107+
</Tooltip>
108+
)}
109+
</Flex>
86110
</DataList.Value>
87111
</DataList.Item>
88112

0 commit comments

Comments
 (0)