From 6f787777441e03c2e005cfb898def8de5ed7fc93 Mon Sep 17 00:00:00 2001 From: soryu Date: Wed, 21 Jan 2026 18:20:26 +0000 Subject: Add standalone task creation from mesh page Allow creating anonymous tasks (tasks without a contract_id) directly from the mesh page: - Add 'Create Standalone Task' option in task creation modal step 1 - Update step 2 to handle null selectedContract for standalone tasks - Show working directory input for standalone tasks - Update TaskList to show standalone tasks (filter: !parentTaskId && (isSupervisor || !contractId)) - Display 'Standalone Tasks' group header with lightning bolt icon - Update empty state message to mention standalone tasks Co-Authored-By: Claude Opus 4.5 --- makima/frontend/src/components/mesh/TaskList.tsx | 23 ++- makima/frontend/src/routes/mesh.tsx | 248 ++++++++++++++--------- 2 files changed, 163 insertions(+), 108 deletions(-) diff --git a/makima/frontend/src/components/mesh/TaskList.tsx b/makima/frontend/src/components/mesh/TaskList.tsx index 80077b6..e3f2862 100644 --- a/makima/frontend/src/components/mesh/TaskList.tsx +++ b/makima/frontend/src/components/mesh/TaskList.tsx @@ -95,8 +95,9 @@ export function TaskList({ // Group tasks by contract and filter by status const groupedTasks = useMemo(() => { - // Separate root tasks (no parent) from subtasks, and only show supervisor tasks - const rootTasks = tasks.filter((t) => !t.parentTaskId && t.isSupervisor); + // Separate root tasks (no parent) from subtasks + // Show supervisor tasks AND standalone tasks (tasks without a contract) + const rootTasks = tasks.filter((t) => !t.parentTaskId && (t.isSupervisor || !t.contractId)); // Filter tasks based on contract status const filteredTasks = statusFilter === 'all' @@ -205,8 +206,8 @@ export function TaskList({ {totalTasks === 0 ? (
{statusFilter === 'all' - ? 'No supervisor tasks yet. Create a contract to start orchestrating tasks.' - : `No ${statusFilter} supervisor tasks found.`} + ? 'No tasks yet. Create a contract or a standalone task to get started.' + : `No ${statusFilter} tasks found.`}
) : (
@@ -229,9 +230,17 @@ export function TaskList({ ) : ( - - Unassigned Tasks ({group.tasks.length}) - + <> + + + + + Standalone Tasks + + + ({group.tasks.length}) + + )}
diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx index 453bdff..77e9d7e 100644 --- a/makima/frontend/src/routes/mesh.tsx +++ b/makima/frontend/src/routes/mesh.tsx @@ -525,15 +525,24 @@ export default function MeshPage() { } }, []); + // Handle creating a standalone task (no contract) + const handleCreateStandaloneTask = useCallback(() => { + setSelectedContract(null); + setNewTaskName("Standalone Task"); + setNewTaskRepoUrl(null); + setNewTaskTargetPath(""); + setModalStep(2); + }, []); + // Create task with configured options const handleCreateTask = useCallback(async () => { - if (creating || !selectedContract) return; + if (creating) return; setShowContractModal(false); setCreating(true); try { const newTask = await saveTask({ - contractId: selectedContract.id, - name: newTaskName || `Task for ${selectedContract.name}`, + contractId: selectedContract?.id, + name: newTaskName || (selectedContract ? `Task for ${selectedContract.name}` : "Standalone Task"), plan: "# Plan\n\nDescribe what this task should accomplish...", repositoryUrl: newTaskRepoUrl || undefined, targetRepoPath: newTaskTargetPath || undefined, @@ -878,120 +887,157 @@ export default function MeshPage() {
{modalStep === 1 ? ( // Step 1: Select Contract - contracts.length === 0 ? ( -
-

No contracts found.

+
+ {/* Standalone task option */} +
- ) : ( -
- {contracts.map((contract) => ( + + {/* Contract selection */} + {contracts.length === 0 ? ( +
+

No contracts found.

- ))} -
- ) +
+ ) : ( +
+
+ Or select a contract: +
+ {contracts.map((contract) => ( + + ))} +
+ )} +
) : ( // Step 2: Configure Task - selectedContract && ( -
- {/* Contract badge */} -
- Contract: - {selectedContract.name} -
+
+ {/* Context badge */} +
+ {selectedContract ? ( + <> + Contract: + {selectedContract.name} + + ) : ( + <> + + + + Standalone Task + + )} +
+ + {/* Task name */} +
+ + 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" + /> +
- {/* Task name */} + {/* Repository selection - only for contract tasks */} + {selectedContract && selectedContract.repositories.length > 0 && (
- - setNewTaskName(e.target.value)} + + +

+ The repository this task will work on. +

+ )} - {/* Repository selection */} - {selectedContract.repositories.length > 0 && ( -
- - -

- The repository this task will work on. -

-
- )} - - {/* Target repo path with DirectoryInput */} - {newTaskRepoUrl && ( -
- - -

- Path where the task will push/merge changes. Leave empty to configure later. -

-
- )} - - {/* Create button */} -
- + {/* Target repo path with DirectoryInput - for standalone tasks or when repo is selected */} + {(newTaskRepoUrl || !selectedContract) && ( +
+ + +

+ {selectedContract + ? "Path where the task will push/merge changes. Leave empty to configure later." + : "Directory for the task to work in. Leave empty to configure later."} +

+ )} + + {/* Create button */} +
+
- ) +
)}
-- cgit v1.2.3