summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/mesh/TaskList.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-06 04:08:11 +0000
committersoryu <soryu@soryu.co>2026-01-11 03:01:13 +0000
commit8b17a175c3e7e27b789812eba4e3cd760beadb10 (patch)
tree7864dcaa2fa9db47fdfd4e8bfdb0b1dde832aa33 /makima/frontend/src/components/mesh/TaskList.tsx
parentf79c416c58557d2f946aa5332989afdfa8c021cd (diff)
downloadsoryu-8b17a175c3e7e27b789812eba4e3cd760beadb10.tar.gz
soryu-8b17a175c3e7e27b789812eba4e3cd760beadb10.zip
Initial Control system
Diffstat (limited to 'makima/frontend/src/components/mesh/TaskList.tsx')
-rw-r--r--makima/frontend/src/components/mesh/TaskList.tsx164
1 files changed, 164 insertions, 0 deletions
diff --git a/makima/frontend/src/components/mesh/TaskList.tsx b/makima/frontend/src/components/mesh/TaskList.tsx
new file mode 100644
index 0000000..a37e564
--- /dev/null
+++ b/makima/frontend/src/components/mesh/TaskList.tsx
@@ -0,0 +1,164 @@
+import type { TaskSummary, TaskStatus } from "../../lib/api";
+
+interface TaskListProps {
+ tasks: TaskSummary[];
+ loading: boolean;
+ onSelect: (id: string) => void;
+ onDelete: (id: string) => void;
+ onCreate: () => void;
+}
+
+function formatDate(dateStr: string): string {
+ const date = new Date(dateStr);
+ return date.toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+}
+
+function getStatusColor(status: TaskStatus): string {
+ switch (status) {
+ case "pending":
+ return "text-[#9bc3ff]";
+ case "running":
+ return "text-green-400";
+ case "paused":
+ return "text-yellow-400";
+ case "blocked":
+ return "text-orange-400";
+ case "done":
+ return "text-emerald-400";
+ case "failed":
+ return "text-red-400";
+ case "merged":
+ return "text-purple-400";
+ default:
+ return "text-[#9bc3ff]";
+ }
+}
+
+function getStatusBgColor(status: TaskStatus): string {
+ switch (status) {
+ case "pending":
+ return "bg-[rgba(117,170,252,0.1)]";
+ case "running":
+ return "bg-green-400/10";
+ case "paused":
+ return "bg-yellow-400/10";
+ case "blocked":
+ return "bg-orange-400/10";
+ case "done":
+ return "bg-emerald-400/10";
+ case "failed":
+ return "bg-red-400/10";
+ case "merged":
+ return "bg-purple-400/10";
+ default:
+ return "bg-[rgba(117,170,252,0.1)]";
+ }
+}
+
+export function TaskList({
+ tasks,
+ loading,
+ onSelect,
+ onDelete,
+ onCreate,
+}: TaskListProps) {
+ if (loading) {
+ return (
+ <div className="panel h-full flex items-center justify-center">
+ <div className="font-mono text-[#9bc3ff] text-sm">Loading tasks...</div>
+ </div>
+ );
+ }
+
+ // Separate root tasks (no parent) from subtasks
+ const rootTasks = tasks.filter((t) => !t.parentTaskId);
+
+ return (
+ <div className="panel h-full flex flex-col">
+ <div className="flex items-center justify-between p-4 pb-2 border-b border-dashed border-[rgba(117,170,252,0.35)]">
+ <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
+ MESH//TASKS
+ </div>
+ <button
+ onClick={onCreate}
+ className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors uppercase"
+ >
+ + New Task
+ </button>
+ </div>
+
+ <div className="flex-1 overflow-y-auto">
+ {rootTasks.length === 0 ? (
+ <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8">
+ No tasks yet. Create one to start orchestrating Claude Code instances.
+ </div>
+ ) : (
+ <div className="divide-y divide-[rgba(117,170,252,0.15)]">
+ {rootTasks.map((task) => (
+ <div
+ key={task.id}
+ className="p-4 hover:bg-[rgba(117,170,252,0.05)] transition-colors"
+ >
+ <div className="flex items-start justify-between gap-4">
+ <button
+ onClick={() => onSelect(task.id)}
+ className="flex-1 text-left"
+ >
+ <div className="flex items-center gap-2 mb-1">
+ <h3 className="font-mono text-sm text-[#dbe7ff]">
+ {task.name}
+ </h3>
+ <span
+ className={`px-2 py-0.5 font-mono text-[10px] uppercase ${getStatusColor(
+ task.status
+ )} ${getStatusBgColor(task.status)} border border-current/20`}
+ >
+ {task.status}
+ </span>
+ {task.depth === 0 && task.subtaskCount > 0 && (
+ <span className="px-2 py-0.5 font-mono text-[10px] text-purple-400 bg-purple-400/10 border border-purple-400/20">
+ Orchestrator
+ </span>
+ )}
+ {task.priority > 0 && (
+ <span className="px-2 py-0.5 font-mono text-[10px] text-orange-400 bg-orange-400/10 border border-orange-400/20">
+ P{task.priority}
+ </span>
+ )}
+ </div>
+ {task.progressSummary && (
+ <p className="font-mono text-xs text-[#9bc3ff] mb-2 line-clamp-2">
+ {task.progressSummary}
+ </p>
+ )}
+ <div className="flex gap-4 font-mono text-[10px] text-[#75aafc]">
+ {task.subtaskCount > 0 && (
+ <span>{task.subtaskCount} subtasks</span>
+ )}
+ <span>{formatDate(task.createdAt)}</span>
+ </div>
+ </button>
+ <button
+ onClick={(e) => {
+ e.stopPropagation();
+ onDelete(task.id);
+ }}
+ className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
+ >
+ Delete
+ </button>
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+}