From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 11 Jan 2026 05:52:14 +0000 Subject: Contract system --- makima/frontend/src/components/mesh/TaskList.tsx | 215 +++++++++++++++++------ 1 file changed, 161 insertions(+), 54 deletions(-) (limited to 'makima/frontend/src/components/mesh/TaskList.tsx') diff --git a/makima/frontend/src/components/mesh/TaskList.tsx b/makima/frontend/src/components/mesh/TaskList.tsx index a37e564..d013782 100644 --- a/makima/frontend/src/components/mesh/TaskList.tsx +++ b/makima/frontend/src/components/mesh/TaskList.tsx @@ -1,4 +1,5 @@ -import type { TaskSummary, TaskStatus } from "../../lib/api"; +import { useMemo } from "react"; +import type { TaskSummary, TaskStatus, ContractPhase } from "../../lib/api"; interface TaskListProps { tasks: TaskSummary[]; @@ -8,6 +9,13 @@ interface TaskListProps { onCreate: () => void; } +interface GroupedTasks { + contractId: string | null; + contractName: string | null; + contractPhase: ContractPhase | null; + tasks: TaskSummary[]; +} + function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString("en-US", { @@ -61,6 +69,17 @@ function getStatusBgColor(status: TaskStatus): string { } } +function getPhaseColor(phase: ContractPhase | null): string { + switch (phase) { + case "research": return "text-blue-400"; + case "specify": return "text-cyan-400"; + case "plan": return "text-yellow-400"; + case "execute": return "text-green-400"; + case "review": return "text-purple-400"; + default: return "text-[#8b949e]"; + } +} + export function TaskList({ tasks, loading, @@ -68,6 +87,53 @@ export function TaskList({ onDelete, onCreate, }: TaskListProps) { + // Group tasks by contract + const groupedTasks = useMemo(() => { + // Separate root tasks (no parent) from subtasks + const rootTasks = tasks.filter((t) => !t.parentTaskId); + + // Group by contractId + const groups = new Map(); + + for (const task of rootTasks) { + const key = task.contractId; + if (!groups.has(key)) { + groups.set(key, { + contractId: task.contractId, + contractName: task.contractName, + contractPhase: task.contractPhase, + tasks: [], + }); + } + groups.get(key)!.tasks.push(task); + } + + // Sort tasks within each group: supervisors first, then by status (running first), then by date + for (const group of groups.values()) { + group.tasks.sort((a, b) => { + // Supervisors always first + if (a.isSupervisor && !b.isSupervisor) return -1; + if (!a.isSupervisor && b.isSupervisor) return 1; + // Running tasks next + if (a.status === "running" && b.status !== "running") return -1; + if (a.status !== "running" && b.status === "running") return 1; + // Then by date (newest first) + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + }); + } + + // Sort: contracts first (alphabetically by name), then orphan tasks + const sorted = Array.from(groups.values()).sort((a, b) => { + // Orphan tasks (no contract) go last + if (!a.contractId && b.contractId) return 1; + if (a.contractId && !b.contractId) return -1; + // Sort by contract name + return (a.contractName || "").localeCompare(b.contractName || ""); + }); + + return sorted; + }, [tasks]); + if (loading) { return (
@@ -76,8 +142,7 @@ export function TaskList({ ); } - // Separate root tasks (no parent) from subtasks - const rootTasks = tasks.filter((t) => !t.parentTaskId); + const totalTasks = groupedTasks.reduce((sum, g) => sum + g.tasks.length, 0); return (
@@ -94,65 +159,107 @@ export function TaskList({
- {rootTasks.length === 0 ? ( + {totalTasks === 0 ? (
No tasks yet. Create one to start orchestrating Claude Code instances.
) : ( -
- {rootTasks.map((task) => ( -
-
- + {/* Supervisor tasks cannot be deleted directly - they are deleted with the contract */} + {!task.isSupervisor && ( + + )} +
- - + ))}
))} -- cgit v1.2.3