summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-22 00:00:58 +0000
committersoryu <soryu@soryu.co>2026-01-22 00:00:58 +0000
commit36fb0b8e169e7209731eb199e74a61cb48474ddd (patch)
treee99b13d0fee092d93e647e418b17b8bb2991468b
parent9e286c146e29e714b3b209b4d948d75cce179b05 (diff)
downloadsoryu-makima/standalone-task-repo-selection.tar.gz
soryu-makima/standalone-task-repo-selection.zip
Add repository selection to standalone task creation in mesh.tsxmakima/standalone-task-repo-selection
- Add 'Standalone Task' option in step 1 of task creation modal - Support creating tasks without requiring a contract first - Add repository type selector (Remote/Local) for standalone tasks - Integrate with getRepositorySuggestions API for repository history - Add DirectoryInput for local path selection with daemon directories - Include optional target repository path configuration New state variables: - isStandalone: boolean for standalone mode - standaloneRepoType: 'remote' | 'local' - standaloneRepoUrl: string for remote URLs - standaloneRepoPath: string for local paths - repoSuggestions: RepositoryHistoryEntry[] for suggestions - showRepoSuggestions: boolean for dropdown visibility Modified handleCreateTask to pass contractId: undefined for standalone tasks and use the standalone repository configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
-rw-r--r--makima/frontend/src/routes/mesh.tsx458
1 files changed, 352 insertions, 106 deletions
diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx
index 453bdff..b53ec20 100644
--- a/makima/frontend/src/routes/mesh.tsx
+++ b/makima/frontend/src/routes/mesh.tsx
@@ -8,8 +8,8 @@ import { UnifiedMeshChatInput } from "../components/mesh/UnifiedMeshChatInput";
import { ContractCompleteQuestion } from "../components/mesh/ContractCompleteQuestion";
import { useTasks } from "../hooks/useTasks";
import { useTaskSubscription, type TaskUpdateEvent, type TaskOutputEvent } from "../hooks/useTaskSubscription";
-import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary } from "../lib/api";
-import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, continueTask as continueTaskApi, resumeSupervisor, branchTask } from "../lib/api";
+import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary, RepositoryHistoryEntry } from "../lib/api";
+import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, getRepositorySuggestions, continueTask as continueTaskApi, resumeSupervisor, branchTask } from "../lib/api";
import { DirectoryInput } from "../components/mesh/DirectoryInput";
import { useAuth } from "../contexts/AuthContext";
import { useSupervisorQuestions } from "../contexts/SupervisorQuestionsContext";
@@ -125,6 +125,13 @@ export default function MeshPage() {
const [newTaskName, setNewTaskName] = useState("");
const [newTaskRepoUrl, setNewTaskRepoUrl] = useState<string | null>(null);
const [newTaskTargetPath, setNewTaskTargetPath] = useState("");
+ // Standalone task mode state
+ const [isStandalone, setIsStandalone] = useState(false);
+ const [standaloneRepoType, setStandaloneRepoType] = useState<"remote" | "local">("remote");
+ const [standaloneRepoUrl, setStandaloneRepoUrl] = useState("");
+ const [standaloneRepoPath, setStandaloneRepoPath] = useState("");
+ const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
+ const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
// Track which subtask's output we're viewing (null = parent task)
const [viewingSubtaskId, setViewingSubtaskId] = useState<string | null>(null);
const [viewingSubtaskName, setViewingSubtaskName] = useState<string | null>(null);
@@ -496,6 +503,13 @@ export default function MeshPage() {
setNewTaskName("");
setNewTaskRepoUrl(null);
setNewTaskTargetPath("");
+ // Reset standalone state
+ setIsStandalone(false);
+ setStandaloneRepoType("remote");
+ setStandaloneRepoUrl("");
+ setStandaloneRepoPath("");
+ setRepoSuggestions([]);
+ setShowRepoSuggestions(false);
setShowContractModal(true);
} catch (e) {
console.error("Failed to load contracts:", e);
@@ -525,18 +539,78 @@ export default function MeshPage() {
}
}, []);
+ // Handle standalone task mode selection
+ const handleSelectStandalone = useCallback(async () => {
+ setIsStandalone(true);
+ setNewTaskName("Standalone Task");
+ setModalStep(2);
+ // Fetch initial repository suggestions for remote type
+ try {
+ const res = await getRepositorySuggestions("remote", undefined, 10);
+ setRepoSuggestions(res.entries);
+ setShowRepoSuggestions(res.entries.length > 0);
+ } catch {
+ setRepoSuggestions([]);
+ setShowRepoSuggestions(false);
+ }
+ }, []);
+
+ // Handle standalone repo type change and fetch suggestions
+ const handleStandaloneRepoTypeChange = useCallback(async (type: "remote" | "local") => {
+ setStandaloneRepoType(type);
+ setStandaloneRepoUrl("");
+ setStandaloneRepoPath("");
+ try {
+ const res = await getRepositorySuggestions(type, undefined, 10);
+ setRepoSuggestions(res.entries);
+ setShowRepoSuggestions(res.entries.length > 0);
+ } catch {
+ setRepoSuggestions([]);
+ setShowRepoSuggestions(false);
+ }
+ }, []);
+
+ // Apply a repository suggestion
+ const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
+ if (suggestion.repositoryUrl) {
+ setStandaloneRepoUrl(suggestion.repositoryUrl);
+ }
+ if (suggestion.localPath) {
+ setStandaloneRepoPath(suggestion.localPath);
+ }
+ setShowRepoSuggestions(false);
+ }, []);
+
// Create task with configured options
const handleCreateTask = useCallback(async () => {
- if (creating || !selectedContract) return;
+ if (creating) return;
+ // For contract-based tasks, require a contract
+ if (!isStandalone && !selectedContract) return;
+
setShowContractModal(false);
setCreating(true);
try {
+ let repositoryUrl: string | undefined;
+ let targetPath: string | undefined;
+
+ if (isStandalone) {
+ // Standalone task - use standalone repo configuration
+ repositoryUrl = standaloneRepoType === "remote"
+ ? (standaloneRepoUrl || undefined)
+ : (standaloneRepoPath || undefined);
+ targetPath = newTaskTargetPath || undefined;
+ } else {
+ // Contract-based task
+ repositoryUrl = newTaskRepoUrl || undefined;
+ targetPath = newTaskTargetPath || undefined;
+ }
+
const newTask = await saveTask({
- contractId: selectedContract.id,
- name: newTaskName || `Task for ${selectedContract.name}`,
+ contractId: isStandalone ? undefined : selectedContract!.id,
+ name: newTaskName || (isStandalone ? "Standalone Task" : `Task for ${selectedContract!.name}`),
plan: "# Plan\n\nDescribe what this task should accomplish...",
- repositoryUrl: newTaskRepoUrl || undefined,
- targetRepoPath: newTaskTargetPath || undefined,
+ repositoryUrl,
+ targetRepoPath: targetPath,
});
if (newTask) {
navigate(`/mesh/${newTask.id}`);
@@ -544,7 +618,7 @@ export default function MeshPage() {
} finally {
setCreating(false);
}
- }, [creating, saveTask, navigate, selectedContract, newTaskName, newTaskRepoUrl, newTaskTargetPath]);
+ }, [creating, saveTask, navigate, isStandalone, selectedContract, newTaskName, newTaskRepoUrl, newTaskTargetPath, standaloneRepoType, standaloneRepoUrl, standaloneRepoPath]);
// Close modal and reset state
const handleCloseModal = useCallback(() => {
@@ -554,6 +628,13 @@ export default function MeshPage() {
setNewTaskName("");
setNewTaskRepoUrl(null);
setNewTaskTargetPath("");
+ // Reset standalone state
+ setIsStandalone(false);
+ setStandaloneRepoType("remote");
+ setStandaloneRepoUrl("");
+ setStandaloneRepoPath("");
+ setRepoSuggestions([]);
+ setShowRepoSuggestions(false);
}, []);
const handleCreateSubtask = useCallback(async () => {
@@ -853,9 +934,13 @@ export default function MeshPage() {
<div className="flex items-center gap-2">
{modalStep === 2 && (
<button
- onClick={() => setModalStep(1)}
+ onClick={() => {
+ setModalStep(1);
+ setIsStandalone(false);
+ setSelectedContract(null);
+ }}
className="text-[#7788aa] hover:text-[#9bc3ff] transition-colors"
- title="Back to contract selection"
+ title="Back to selection"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
@@ -863,7 +948,7 @@ export default function MeshPage() {
</button>
)}
<h2 className="text-sm font-mono uppercase tracking-wide text-[#9bc3ff]">
- {modalStep === 1 ? "Select Contract" : "Configure Task"}
+ {modalStep === 1 ? "Create Task" : isStandalone ? "Standalone Task" : "Configure Task"}
</h2>
</div>
<button
@@ -877,121 +962,282 @@ export default function MeshPage() {
</div>
<div className="p-4 overflow-y-auto flex-1">
{modalStep === 1 ? (
- // Step 1: Select Contract
- contracts.length === 0 ? (
- <div className="text-center py-8">
- <p className="text-[#7788aa] font-mono text-xs mb-4">No contracts found.</p>
- <button
- onClick={() => {
- handleCloseModal();
- navigate("/contracts");
- }}
- className="px-4 py-2 bg-[#3f6fb3] border border-[#75aafc] text-white font-mono text-xs uppercase tracking-wide hover:bg-[#4a7fc3] transition-colors"
- >
- Create Contract
- </button>
+ // Step 1: Select Contract or Standalone
+ <div className="space-y-4">
+ {/* Standalone Task Option */}
+ <button
+ onClick={handleSelectStandalone}
+ className="w-full text-left p-3 border border-[rgba(117,170,252,0.35)] bg-[#0a1525] hover:border-[#75aafc] hover:bg-[#0d1b2d] transition-colors"
+ >
+ <div className="flex items-center justify-between">
+ <span className="text-[#9bc3ff] font-mono text-xs font-semibold">Standalone Task</span>
+ <span className="text-[10px] font-mono uppercase px-2 py-0.5 border border-[#75aafc] text-[#75aafc] bg-[rgba(117,170,252,0.1)]">
+ Quick
+ </span>
+ </div>
+ <p className="text-[10px] font-mono text-[#7788aa] mt-1">
+ Create a task without a contract. Configure repository directly.
+ </p>
+ </button>
+
+ {/* Divider */}
+ <div className="flex items-center gap-3">
+ <div className="flex-1 border-t border-[rgba(117,170,252,0.15)]" />
+ <span className="text-[10px] font-mono text-[#556677] uppercase">Or select contract</span>
+ <div className="flex-1 border-t border-[rgba(117,170,252,0.15)]" />
</div>
- ) : (
+
+ {/* Contract list */}
+ {contracts.length === 0 ? (
+ <div className="text-center py-4">
+ <p className="text-[#7788aa] font-mono text-xs mb-4">No contracts found.</p>
+ <button
+ onClick={() => {
+ handleCloseModal();
+ navigate("/contracts");
+ }}
+ className="px-4 py-2 bg-[#3f6fb3] border border-[#75aafc] text-white font-mono text-xs uppercase tracking-wide hover:bg-[#4a7fc3] transition-colors"
+ >
+ Create Contract
+ </button>
+ </div>
+ ) : (
+ <div className="space-y-2">
+ {contracts.map((contract) => (
+ <button
+ key={contract.id}
+ onClick={() => handleSelectContract(contract)}
+ className="w-full text-left p-3 border border-[rgba(117,170,252,0.15)] bg-[#0a1525] hover:border-[rgba(117,170,252,0.35)] hover:bg-[#0d1b2d] transition-colors"
+ >
+ <div className="flex items-center justify-between">
+ <span className="text-[#9bc3ff] font-mono text-xs">{contract.name}</span>
+ <span className="text-[10px] font-mono uppercase px-2 py-0.5 border border-[rgba(117,170,252,0.25)] text-[#75aafc]">
+ {contract.phase}
+ </span>
+ </div>
+ {contract.description && (
+ <p className="text-[10px] font-mono text-[#7788aa] mt-1 line-clamp-2">{contract.description}</p>
+ )}
+ <div className="flex gap-3 mt-2 text-[10px] font-mono text-[#556677]">
+ <span>{contract.taskCount} tasks</span>
+ <span>{contract.repositoryCount} repos</span>
+ </div>
+ </button>
+ ))}
+ </div>
+ )}
+ </div>
+ ) : isStandalone ? (
+ // Step 2: Configure Standalone Task
+ <div className="space-y-4">
+ {/* Standalone badge */}
+ <div className="flex items-center gap-2 text-xs font-mono text-[#7788aa]">
+ <span className="px-2 py-0.5 border border-[#75aafc] text-[#75aafc] bg-[rgba(117,170,252,0.1)] text-[10px] uppercase">
+ Standalone
+ </span>
+ <span className="text-[#556677]">No contract</span>
+ </div>
+
+ {/* Task name */}
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Task Name</label>
+ <input
+ type="text"
+ value={newTaskName}
+ onChange={(e) => setNewTaskName(e.target.value)}
+ className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]"
+ placeholder="Task name"
+ />
+ </div>
+
+ {/* Repository type selector */}
<div className="space-y-2">
- {contracts.map((contract) => (
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Repository Type</label>
+ <div className="flex gap-2">
<button
- key={contract.id}
- onClick={() => handleSelectContract(contract)}
- className="w-full text-left p-3 border border-[rgba(117,170,252,0.15)] bg-[#0a1525] hover:border-[rgba(117,170,252,0.35)] hover:bg-[#0d1b2d] transition-colors"
+ type="button"
+ onClick={() => handleStandaloneRepoTypeChange("remote")}
+ className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
+ standaloneRepoType === "remote"
+ ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
+ : "bg-[#0a1525] text-[#8b949e] border border-[rgba(117,170,252,0.25)] hover:border-[#75aafc]"
+ }`}
>
- <div className="flex items-center justify-between">
- <span className="text-[#9bc3ff] font-mono text-xs">{contract.name}</span>
- <span className="text-[10px] font-mono uppercase px-2 py-0.5 border border-[rgba(117,170,252,0.25)] text-[#75aafc]">
- {contract.phase}
- </span>
- </div>
- {contract.description && (
- <p className="text-[10px] font-mono text-[#7788aa] mt-1 line-clamp-2">{contract.description}</p>
- )}
- <div className="flex gap-3 mt-2 text-[10px] font-mono text-[#556677]">
- <span>{contract.taskCount} tasks</span>
- <span>{contract.repositoryCount} repos</span>
- </div>
+ Remote
</button>
- ))}
+ <button
+ type="button"
+ onClick={() => handleStandaloneRepoTypeChange("local")}
+ className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
+ standaloneRepoType === "local"
+ ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
+ : "bg-[#0a1525] text-[#8b949e] border border-[rgba(117,170,252,0.25)] hover:border-[#75aafc]"
+ }`}
+ >
+ Local
+ </button>
+ </div>
</div>
- )
- ) : (
- // Step 2: Configure Task
- selectedContract && (
- <div className="space-y-4">
- {/* Contract badge */}
- <div className="flex items-center gap-2 text-xs font-mono text-[#7788aa]">
- <span>Contract:</span>
- <span className="text-[#9bc3ff]">{selectedContract.name}</span>
+
+ {/* Repository suggestions */}
+ {showRepoSuggestions && repoSuggestions.length > 0 && (
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">
+ Recent Repositories
+ </label>
+ <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto">
+ {repoSuggestions.map((suggestion) => (
+ <button
+ key={suggestion.id}
+ type="button"
+ onClick={() => applyRepoSuggestion(suggestion)}
+ className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
+ >
+ <div className="flex items-center justify-between">
+ <span className="text-[#9bc3ff] truncate">{suggestion.name}</span>
+ <span className="text-[10px] text-[#556677] ml-2">
+ {suggestion.useCount}×
+ </span>
+ </div>
+ <div className="text-[10px] text-[#556677] truncate">
+ {standaloneRepoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
+ </div>
+ </button>
+ ))}
+ </div>
</div>
+ )}
- {/* Task name */}
+ {/* Repository URL/Path input */}
+ {standaloneRepoType === "remote" ? (
<div className="space-y-1">
- <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Task Name</label>
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Repository URL</label>
<input
type="text"
- value={newTaskName}
- onChange={(e) => setNewTaskName(e.target.value)}
+ value={standaloneRepoUrl}
+ onChange={(e) => setStandaloneRepoUrl(e.target.value)}
className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]"
- placeholder="Task name"
+ placeholder="https://github.com/user/repo.git"
+ />
+ <p className="text-[10px] font-mono text-[#556677]">
+ GitHub, GitLab, or any Git repository URL.
+ </p>
+ </div>
+ ) : (
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Local Path</label>
+ <DirectoryInput
+ value={standaloneRepoPath}
+ onChange={setStandaloneRepoPath}
+ suggestions={daemonDirectories}
+ placeholder="/path/to/repository"
/>
+ <p className="text-[10px] font-mono text-[#556677]">
+ Path to an existing local repository.
+ </p>
</div>
+ )}
+
+ {/* Target repo path (optional) */}
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Target Repository Path (Optional)</label>
+ <DirectoryInput
+ value={newTaskTargetPath}
+ onChange={setNewTaskTargetPath}
+ suggestions={daemonDirectories}
+ placeholder="/path/to/your/local/repo"
+ repoUrl={standaloneRepoType === "remote" ? standaloneRepoUrl : null}
+ />
+ <p className="text-[10px] font-mono text-[#556677]">
+ Path where the task will push/merge changes. Leave empty to configure later.
+ </p>
+ </div>
- {/* Repository selection */}
- {selectedContract.repositories.length > 0 && (
- <div className="space-y-1">
- <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Repository</label>
- <select
- value={newTaskRepoUrl || ""}
- onChange={(e) => setNewTaskRepoUrl(e.target.value || null)}
- className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]"
- >
- <option value="">No repository</option>
- {selectedContract.repositories
- .filter((r) => r.status === "ready")
- .map((repo) => (
- <option key={repo.id} value={repo.repositoryUrl || repo.localPath || ""}>
- {repo.name}
- {repo.isPrimary && " (primary)"}
- </option>
- ))}
- </select>
- <p className="text-[10px] font-mono text-[#556677]">
- The repository this task will work on.
- </p>
- </div>
- )}
+ {/* Create button */}
+ <div className="pt-2">
+ <button
+ onClick={handleCreateTask}
+ disabled={creating}
+ className="w-full px-4 py-2 bg-[#3f6fb3] border border-[#75aafc] text-white font-mono text-xs uppercase tracking-wide hover:bg-[#4a7fc3] disabled:opacity-50 transition-colors"
+ >
+ {creating ? "Creating..." : "Create Task"}
+ </button>
+ </div>
+ </div>
+ ) : selectedContract && (
+ // Step 2: Configure Task (Contract-based)
+ <div className="space-y-4">
+ {/* Contract badge */}
+ <div className="flex items-center gap-2 text-xs font-mono text-[#7788aa]">
+ <span>Contract:</span>
+ <span className="text-[#9bc3ff]">{selectedContract.name}</span>
+ </div>
- {/* Target repo path with DirectoryInput */}
- {newTaskRepoUrl && (
- <div className="space-y-1">
- <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Target Repository Path</label>
- <DirectoryInput
- value={newTaskTargetPath}
- onChange={setNewTaskTargetPath}
- suggestions={daemonDirectories}
- placeholder="/path/to/your/local/repo"
- repoUrl={newTaskRepoUrl}
- />
- <p className="text-[10px] font-mono text-[#556677]">
- Path where the task will push/merge changes. Leave empty to configure later.
- </p>
- </div>
- )}
+ {/* Task name */}
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Task Name</label>
+ <input
+ type="text"
+ value={newTaskName}
+ onChange={(e) => setNewTaskName(e.target.value)}
+ className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]"
+ placeholder="Task name"
+ />
+ </div>
- {/* Create button */}
- <div className="pt-2">
- <button
- onClick={handleCreateTask}
- disabled={creating}
- className="w-full px-4 py-2 bg-[#3f6fb3] border border-[#75aafc] text-white font-mono text-xs uppercase tracking-wide hover:bg-[#4a7fc3] disabled:opacity-50 transition-colors"
+ {/* Repository selection */}
+ {selectedContract.repositories.length > 0 && (
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Repository</label>
+ <select
+ value={newTaskRepoUrl || ""}
+ onChange={(e) => setNewTaskRepoUrl(e.target.value || null)}
+ className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]"
>
- {creating ? "Creating..." : "Create Task"}
- </button>
+ <option value="">No repository</option>
+ {selectedContract.repositories
+ .filter((r) => r.status === "ready")
+ .map((repo) => (
+ <option key={repo.id} value={repo.repositoryUrl || repo.localPath || ""}>
+ {repo.name}
+ {repo.isPrimary && " (primary)"}
+ </option>
+ ))}
+ </select>
+ <p className="text-[10px] font-mono text-[#556677]">
+ The repository this task will work on.
+ </p>
</div>
+ )}
+
+ {/* Target repo path with DirectoryInput */}
+ {newTaskRepoUrl && (
+ <div className="space-y-1">
+ <label className="block text-[10px] font-mono uppercase tracking-wide text-[#7788aa]">Target Repository Path</label>
+ <DirectoryInput
+ value={newTaskTargetPath}
+ onChange={setNewTaskTargetPath}
+ suggestions={daemonDirectories}
+ placeholder="/path/to/your/local/repo"
+ repoUrl={newTaskRepoUrl}
+ />
+ <p className="text-[10px] font-mono text-[#556677]">
+ Path where the task will push/merge changes. Leave empty to configure later.
+ </p>
+ </div>
+ )}
+
+ {/* Create button */}
+ <div className="pt-2">
+ <button
+ onClick={handleCreateTask}
+ disabled={creating}
+ className="w-full px-4 py-2 bg-[#3f6fb3] border border-[#75aafc] text-white font-mono text-xs uppercase tracking-wide hover:bg-[#4a7fc3] disabled:opacity-50 transition-colors"
+ >
+ {creating ? "Creating..." : "Create Task"}
+ </button>
</div>
- )
+ </div>
)}
</div>
</div>