import { useState, useCallback, useEffect, useMemo } from "react"; import { useParams, useNavigate } from "react-router"; import { ReactFlow, Edge, Controls, Background, Handle, Position, BackgroundVariant, MarkerType, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import { Masthead } from "../components/Masthead"; import { useDirectives, useDirectiveEventSubscription } from "../hooks/useDirectives"; import { useAuth } from "../contexts/AuthContext"; import type { DirectiveSummary, DirectiveWithProgress, DirectiveGraphResponse, DirectiveGraphNode, CreateDirectiveRequest, RepositoryHistoryEntry, AutonomyLevel, StepStatus, ConfidenceLevel, } from "../lib/api"; import { getRepositorySuggestions } from "../lib/api"; export default function DirectivesPage() { const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); const navigate = useNavigate(); // Redirect to login if not authenticated (when auth is configured) useEffect(() => { if (!authLoading && isAuthConfigured && !isAuthenticated) { navigate("/login"); } }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); // Show loading while checking auth if (authLoading) { return (

Loading...

); } // Don't render if not authenticated (will redirect) if (isAuthConfigured && !isAuthenticated) { return null; } return ; } function DirectivesPageContent() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { directives, loading, error, createNewDirective, archiveExistingDirective, getDirectiveById, getGraph, start, pause, resume, stop, } = useDirectives(); const [directiveDetail, setDirectiveDetail] = useState(null); const [directiveGraph, setDirectiveGraph] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); // Load directive detail when ID changes useEffect(() => { if (id) { setDetailLoading(true); Promise.all([getDirectiveById(id), getGraph(id).catch(() => null)]).then(([directive, graph]) => { setDirectiveDetail(directive); setDirectiveGraph(graph); setDetailLoading(false); }); } else { setDirectiveDetail(null); setDirectiveGraph(null); } }, [id, getDirectiveById, getGraph]); const handleSelect = useCallback( (directiveId: string) => { navigate(`/directives/${directiveId}`); }, [navigate] ); const handleBack = useCallback(() => { navigate("/directives"); }, [navigate]); const handleCreate = useCallback(() => { setIsCreating(true); }, []); const handleCreateSubmit = useCallback( async (goal: string, repositoryUrl: string | undefined, autonomyLevel: AutonomyLevel) => { const data: CreateDirectiveRequest = { goal: goal.trim(), repositoryUrl: repositoryUrl?.trim() || undefined, autonomyLevel, }; try { const result = await createNewDirective(data); if (result) { setIsCreating(false); navigate(`/directives/${result.id}`); } } catch (err) { console.error("Failed to create directive:", err); } }, [createNewDirective, navigate] ); const handleCreateCancel = useCallback(() => { setIsCreating(false); }, []); const handleArchive = useCallback( async (directive: DirectiveSummary) => { if (confirm(`Are you sure you want to archive this directive?`)) { const success = await archiveExistingDirective(directive.id); if (success && directive.id === id) { navigate("/directives"); } } }, [archiveExistingDirective, id, navigate] ); const handleRefresh = useCallback(async () => { if (id) { const [directive, graph] = await Promise.all([ getDirectiveById(id), getGraph(id).catch(() => null), ]); setDirectiveDetail(directive); setDirectiveGraph(graph); } }, [id, getDirectiveById, getGraph]); const handleStart = useCallback(async () => { if (id) { await start(id); handleRefresh(); } }, [id, start, handleRefresh]); const handlePause = useCallback(async () => { if (id) { await pause(id); handleRefresh(); } }, [id, pause, handleRefresh]); const handleResume = useCallback(async () => { if (id) { await resume(id); handleRefresh(); } }, [id, resume, handleRefresh]); const handleStop = useCallback(async () => { if (id) { await stop(id); handleRefresh(); } }, [id, stop, handleRefresh]); return (
{error && (
{error}
)} {/* Create directive modal */} {isCreating && ( )}
{/* Directive list */} {/* Directive detail or empty state */} {directiveDetail ? ( ) : (

Select a directive or create a new one

)}
); } // ============================================================================= // Directive List Component // ============================================================================= interface DirectiveListProps { directives: DirectiveSummary[]; loading: boolean; onSelect: (id: string) => void; onCreate: () => void; selectedId?: string; onArchive: (directive: DirectiveSummary) => void; } function DirectiveList({ directives, loading, onSelect, onCreate, selectedId, onArchive, }: DirectiveListProps) { const [filter, setFilter] = useState<"all" | "active" | "completed" | "failed">("all"); const filteredDirectives = directives.filter((d) => { if (filter === "all") return true; if (filter === "active") return ["draft", "planning", "active", "paused"].includes(d.status); if (filter === "completed") return d.status === "completed"; if (filter === "failed") return d.status === "failed"; return true; }); return (

Directives

{/* Filters */}
{(["all", "active", "completed", "failed"] as const).map((f) => ( ))}
{/* List */}
{loading ? (

Loading...

) : filteredDirectives.length === 0 ? (

No directives found

) : ( filteredDirectives.map((d) => ( onSelect(d.id)} onArchive={() => onArchive(d)} /> )) )}
); } interface DirectiveListItemProps { directive: DirectiveSummary; selected: boolean; onClick: () => void; onArchive: () => void; } function DirectiveListItem({ directive, selected, onClick, onArchive }: DirectiveListItemProps) { const progress = directive.totalSteps > 0 ? Math.round((directive.completedSteps / directive.totalSteps) * 100) : 0; const statusColor = { draft: "text-[#556677]", planning: "text-yellow-400", active: "text-green-400", paused: "text-yellow-400", completed: "text-[#75aafc]", archived: "text-[#556677]", failed: "text-red-400", }[directive.status] || "text-[#556677]"; const confidenceColor = { green: "bg-green-500", yellow: "bg-yellow-500", red: "bg-red-500", }[directive.currentConfidence !== null && directive.currentConfidence >= 0.8 ? "green" : directive.currentConfidence !== null && directive.currentConfidence >= 0.5 ? "yellow" : "red"] || "bg-[#556677]"; return (
{directive.title || directive.goal.slice(0, 50)}
{directive.status} {directive.completedSteps}/{directive.totalSteps} steps
{directive.currentConfidence !== null && (
)}
{/* Progress bar */} {directive.totalSteps > 0 && (
)}
); } // ============================================================================= // Directive Detail Component // ============================================================================= interface DirectiveDetailProps { directive: DirectiveWithProgress; graph: DirectiveGraphResponse | null; loading: boolean; onBack: () => void; onRefresh: () => void; onStart: () => void; onPause: () => void; onResume: () => void; onStop: () => void; } function DirectiveDetail({ directive, graph, loading, onBack, onRefresh, onStart, onPause, onResume, onStop, }: DirectiveDetailProps) { const [activeTab, setActiveTab] = useState<"overview" | "chain" | "events" | "evaluations" | "approvals" | "verifiers">("overview"); if (loading) { return (

Loading...

); } const statusColor = { draft: "text-[#556677] bg-[#556677]/10 border-[#556677]/30", planning: "text-yellow-400 bg-yellow-400/10 border-yellow-400/30", active: "text-green-400 bg-green-400/10 border-green-400/30", paused: "text-yellow-400 bg-yellow-400/10 border-yellow-400/30", completed: "text-[#75aafc] bg-[#75aafc]/10 border-[#75aafc]/30", archived: "text-[#556677] bg-[#556677]/10 border-[#556677]/30", failed: "text-red-400 bg-red-400/10 border-red-400/30", }[directive.status] || "text-[#556677] bg-[#556677]/10 border-[#556677]/30"; return (
{/* Header */}

{directive.title || directive.goal.slice(0, 50)}

{directive.status}
{directive.status === "draft" && ( )} {directive.status === "active" && ( )} {directive.status === "paused" && ( )} {["active", "paused"].includes(directive.status) && ( )}
{/* Tabs */}
{(["overview", "chain", "events", "evaluations", "approvals", "verifiers"] as const).map((tab) => ( ))}
{/* Tab Content */}
{activeTab === "overview" && ( )} {activeTab === "chain" && ( )} {activeTab === "events" && ( )} {activeTab === "evaluations" && ( )} {activeTab === "approvals" && ( )} {activeTab === "verifiers" && ( )}
); } // ============================================================================= // Tab Components // ============================================================================= function OverviewTab({ directive }: { directive: DirectiveWithProgress }) { return (
{/* Goal */}

Goal

{directive.goal}

{/* Progress */}

Progress

{directive.chain?.completedSteps || 0}
Completed Steps
{directive.chain?.totalSteps || 0}
Total Steps
{directive.chain?.currentConfidence != null ? `${Math.round((directive.chain?.currentConfidence ?? 0) * 100)}%` : "-"}
Confidence
{/* Configuration */}

Configuration

Autonomy Level {directive.autonomyLevel}
Max Rework Cycles {directive.maxReworkCycles}
Green Threshold {directive.confidenceThresholdGreen}
Yellow Threshold {directive.confidenceThresholdYellow}
{/* Repository */} {directive.repositoryUrl && (

Repository

{directive.repositoryUrl}

)}
); } // Step status colors for both list and DAG views const stepStatusStyles: Record = { pending: { border: "#556677", bg: "#556677", text: "#556677" }, ready: { border: "#3b82f6", bg: "#3b82f6", text: "#3b82f6" }, running: { border: "#22c55e", bg: "#22c55e", text: "#22c55e" }, evaluating: { border: "#eab308", bg: "#eab308", text: "#eab308" }, passed: { border: "#75aafc", bg: "#75aafc", text: "#75aafc" }, failed: { border: "#ef4444", bg: "#ef4444", text: "#ef4444" }, rework: { border: "#f97316", bg: "#f97316", text: "#f97316" }, skipped: { border: "#556677", bg: "#556677", text: "#556677" }, blocked: { border: "#ef4444", bg: "#ef4444", text: "#ef4444" }, }; // Confidence level colors const confidenceColors: Record = { green: "#22c55e", yellow: "#eab308", red: "#ef4444", }; // Node dimensions const NODE_WIDTH = 180; const NODE_HEIGHT = 70; // Custom node component for steps function StepNodeComponent({ data }: { data: DirectiveGraphNode & { selected?: boolean } }) { const styles = stepStatusStyles[data.status] || stepStatusStyles.pending; const isRunning = data.status === "running" || data.status === "evaluating"; return (
{/* Status indicator bar */}
{/* Content */}
{data.name} {data.confidenceScore !== null && data.confidenceLevel && (
)}
{data.status} {data.stepType}
); } // Node types for React Flow const nodeTypes = { step: StepNodeComponent, }; function ChainTab({ directive, graph }: { directive: DirectiveWithProgress; graph: DirectiveGraphResponse | null }) { const [viewMode, setViewMode] = useState<"dag" | "list">("dag"); // Convert graph to React Flow nodes and edges const { nodes, edges } = useMemo(() => { if (!graph || !graph.nodes.length) { // Fallback: generate positions from directive.steps const stepNodes = directive.steps.map((step, index) => ({ id: step.id, type: "step" as const, position: { x: (index % 3) * 220 + 50, y: Math.floor(index / 3) * 120 + 50, }, data: { id: step.id, name: step.name, stepType: step.stepType, status: step.status, confidenceScore: step.confidenceScore, confidenceLevel: step.confidenceLevel, contractId: step.contractId, editorX: null, editorY: null, }, })); // Build edges from dependencies const stepEdges: Edge[] = []; directive.steps.forEach((step) => { step.dependsOn.forEach((depName) => { const depStep = directive.steps.find((s) => s.name === depName); if (depStep) { stepEdges.push({ id: `${depStep.id}-${step.id}`, source: depStep.id, target: step.id, type: "smoothstep", markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" }, style: { stroke: "#556677", strokeWidth: 2 }, }); } }); }); return { nodes: stepNodes, edges: stepEdges }; } // Use graph data const graphNodes = graph.nodes.map((node) => ({ id: node.id, type: "step" as const, position: { x: node.editorX ?? 50, y: node.editorY ?? 50, }, data: { ...node }, })); const graphEdges: Edge[] = graph.edges.map((edge) => ({ id: `${edge.source}-${edge.target}`, source: edge.source, target: edge.target, type: "smoothstep", markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" }, style: { stroke: "#556677", strokeWidth: 2 }, })); return { nodes: graphNodes, edges: graphEdges }; }, [graph, directive.steps]); if (!directive.chain) { return (

No chain generated yet. Start the directive to generate a chain.

); } return (
{/* Chain info header */}
{directive.chain.name}
Generation {directive.chain.generation}
{directive.chain.completedSteps}/{directive.chain.totalSteps} steps
{/* View toggle */}
{viewMode === "dag" ? ( /* DAG visualization */
{directive.steps.length === 0 ? (

No steps in chain

) : ( )}
) : ( /* List view */

Steps

{directive.steps.length === 0 ? (

No steps in chain

) : ( directive.steps.map((step) => { const styles = stepStatusStyles[step.status] || stepStatusStyles.pending; return (
{step.name} {step.status} {step.confidenceScore !== null && ( ({Math.round(step.confidenceScore * 100)}%) )}
{step.stepType}
{step.description && (

{step.description}

)} {step.dependsOn.length > 0 && (
Depends on: {step.dependsOn.join(", ")}
)}
); }) )}
)}
); } function EventsTab({ directive }: { directive: DirectiveWithProgress }) { // Subscribe to real-time events via SSE const { events: streamEvents, isConnected, error: sseError } = useDirectiveEventSubscription(directive.id); // Combine initial events with streamed events (avoiding duplicates) const allEvents = useMemo(() => { const eventMap = new Map(); // Add initial events first directive.recentEvents.forEach((e) => eventMap.set(e.id, e)); // Add streamed events (will override any duplicates) streamEvents.forEach((e) => eventMap.set(e.id, e)); // Sort by created_at descending (most recent first) return Array.from(eventMap.values()).sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); }, [directive.recentEvents, streamEvents]); return (
{/* Connection status */}
{isConnected ? "● Live" : "○ Connecting..."} {sseError && {sseError}}
{allEvents.length} events
{/* Event list */} {allEvents.length === 0 ? (

No events yet

) : (
{allEvents.map((event) => { const severityColors: Record = { info: "text-[#75aafc]", warning: "text-yellow-400", error: "text-red-400", critical: "text-red-600", }; const severityColor = severityColors[event.severity] || "text-[#556677]"; return (
{event.eventType} {event.actorType}
{new Date(event.createdAt).toLocaleString()}
{event.eventData != null && (
                    {JSON.stringify(event.eventData, null, 2)}
                  
)}
); })}
)}
); } function EvaluationsTab({ directive: _directive }: { directive: DirectiveWithProgress }) { // TODO: Fetch evaluations separately return (

Evaluations will be shown here after steps are evaluated

); } function ApprovalsTab({ directive, onRefresh }: { directive: DirectiveWithProgress; onRefresh: () => void }) { if (directive.pendingApprovals.length === 0) { return (

No pending approvals

); } const handleApprove = async (approvalId: string) => { try { const { approveDirectiveRequest } = await import("../lib/api"); await approveDirectiveRequest(directive.id, approvalId); onRefresh(); } catch (err) { console.error("Failed to approve:", err); } }; const handleDeny = async (approvalId: string) => { try { const { denyDirectiveRequest } = await import("../lib/api"); await denyDirectiveRequest(directive.id, approvalId); onRefresh(); } catch (err) { console.error("Failed to deny:", err); } }; return (
{directive.pendingApprovals.map((approval) => { const urgencyColor = { low: "text-[#556677]", normal: "text-[#75aafc]", high: "text-yellow-400", critical: "text-red-400", }[approval.urgency] || "text-[#556677]"; return (
{approval.approvalType} {approval.urgency}

{approval.description}

); })}
); } function VerifiersTab({ directive: _directive }: { directive: DirectiveWithProgress }) { // TODO: Fetch verifiers separately return (

Verifiers will be shown here. Use auto-detect to find available verifiers.

); } // ============================================================================= // Create Directive Modal // ============================================================================= interface CreateDirectiveModalProps { onSubmit: (goal: string, repositoryUrl: string | undefined, autonomyLevel: AutonomyLevel) => void; onCancel: () => void; } function CreateDirectiveModal({ onSubmit, onCancel }: CreateDirectiveModalProps) { const [goal, setGoal] = useState(""); const [repositoryUrl, setRepositoryUrl] = useState(""); const [autonomyLevel, setAutonomyLevel] = useState("guardrails"); const [suggestions, setSuggestions] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); // Load suggestions useEffect(() => { getRepositorySuggestions("remote", undefined, 5) .then((res) => { setSuggestions(res.entries); }) .catch(() => { setSuggestions([]); }); }, []); const handleSubmit = () => { if (goal.trim()) { onSubmit(goal.trim(), repositoryUrl.trim() || undefined, autonomyLevel); } }; return (

Create Directive

{/* Goal */}