"use client"; // Sidebar for the Missions tier — the unified list that replaces // ResearchList + LoopsList after Slice 9's cutover. During Slice 2 it // runs in parallel with the old two lists so we can iterate on the // UX without breaking existing workflows. import { useCallback, useEffect, useState } from "react"; import { Plus, RotateCw, Trash2, Wrench } from "lucide-react"; import { deleteMission, listMissions, type MissionListItem, type MissionStatus, type PhaseKind, type TemplateKind, } from "@/lib/api/missions"; import { MissionWizard } from "./MissionWizard"; const mono = "ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Consolas, monospace"; const STATUS_COLOR: Record = { draft: "#8a8a92", running: "#5ec8d8", completed: "#5fd08a", failed: "#ff8a7a", cancelled: "#6a6a72", }; const TEMPLATE_BADGE: Record = { research_only: { label: "RESEARCH", color: "#7cd6e0" }, research_and_code: { label: "R+CODE", color: "#c98af0" }, security_hardening: { label: "SECURITY", color: "#ff8a7a" }, refactor: { label: "REFACTOR", color: "#ffb44a" }, benchmark: { label: "BENCHMARK", color: "#83e6a5" }, custom: { label: "CUSTOM", color: "#8a8a92" }, }; export function MissionsList({ selectedId, onSelect, refreshKey, onCreated, onDeleted, }: { selectedId: string | null; onSelect: (id: string) => void; refreshKey: number; onCreated: (id: string) => void; /** Called after bulk delete completes; parent clears selection if the * currently-open mission was among the deleted rows. */ onDeleted?: (deletedIds: string[]) => void; }) { const [missions, setMissions] = useState([]); const [loading, setLoading] = useState(true); const [wizardOpen, setWizardOpen] = useState(false); const [error, setError] = useState(null); const [selectMode, setSelectMode] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const [bulkBusy, setBulkBusy] = useState(false); const load = useCallback(async () => { setError(null); try { const rows = await listMissions(100); setMissions(rows); } catch (e) { setError(e instanceof Error ? e.message : "load failed"); } finally { setLoading(false); } }, []); useEffect(() => { let alive = true; (async () => { if (alive) await load(); })(); return () => { alive = false; }; }, [load, refreshKey]); return ( <>
MISSIONS · {missions.length}
{selectMode && selectedIds.size > 0 && ( )}
{loading ? (
Loading…
) : error ? (
{error}
) : missions.length === 0 ? (
No missions yet. Pick a workflow template to get started.
) : ( missions.map((m) => { const active = !selectMode && selectedId === m.id; const picked = selectMode && selectedIds.has(m.id); const badge = TEMPLATE_BADGE[m.template_kind] ?? TEMPLATE_BADGE.custom; return ( ); }) )}
{wizardOpen && ( setWizardOpen(false)} onCreated={(id) => { setWizardOpen(false); onCreated(id); void load(); }} /> )} ); } const PHASE_LABEL: Record = { research: "Research", coding: "Coding", benchmark: "Benchmark", security_scan: "Security", }; const iconBtn: React.CSSProperties = { width: 26, height: 26, borderRadius: 7, border: "1px solid rgba(255,255,255,.12)", background: "transparent", color: "#9a9aa2", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", }; const ctaBtn: React.CSSProperties = { display: "inline-flex", alignItems: "center", gap: 6, padding: "8px 12px", borderRadius: 9, border: 0, background: "#ff8a7a", color: "#1a0d0b", fontSize: 12.5, fontWeight: 700, cursor: "pointer", };