From 8f725a7c64fbeb85ebeb59b54d2f774e9a0a59d6 Mon Sep 17 00:00:00 2001 From: soryu Date: Fri, 6 Feb 2026 01:02:32 +0000 Subject: Fix: Directive page and remove chain page --- makima/frontend/src/components/NavStrip.tsx | 1 - .../frontend/src/components/chains/ChainEditor.tsx | 1278 -------------------- .../frontend/src/components/chains/ChainList.tsx | 191 --- makima/frontend/src/hooks/useChains.ts | 145 --- makima/frontend/src/hooks/useDirectives.ts | 2 +- makima/frontend/src/main.tsx | 17 - makima/frontend/src/routes/chains.tsx | 496 -------- makima/frontend/src/routes/directives.tsx | 4 +- 8 files changed, 3 insertions(+), 2131 deletions(-) delete mode 100644 makima/frontend/src/components/chains/ChainEditor.tsx delete mode 100644 makima/frontend/src/components/chains/ChainList.tsx delete mode 100644 makima/frontend/src/hooks/useChains.ts delete mode 100644 makima/frontend/src/routes/chains.tsx (limited to 'makima') diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index ece07e4..9bb7777 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -11,7 +11,6 @@ interface NavLink { const NAV_LINKS: NavLink[] = [ { label: "Listen", href: "/listen" }, { label: "Directives", href: "/directives", requiresAuth: true }, - { label: "Chains", href: "/chains", requiresAuth: true }, { label: "Contracts", href: "/contracts", requiresAuth: true }, { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, diff --git a/makima/frontend/src/components/chains/ChainEditor.tsx b/makima/frontend/src/components/chains/ChainEditor.tsx deleted file mode 100644 index 6b9aa70..0000000 --- a/makima/frontend/src/components/chains/ChainEditor.tsx +++ /dev/null @@ -1,1278 +0,0 @@ -import { useState, useCallback, useEffect } from "react"; -import { - ReactFlow, - Node, - Edge, - Controls, - Background, - useNodesState, - useEdgesState, - Connection, - Handle, - Position, - BackgroundVariant, - NodeChange, - MarkerType, -} from "@xyflow/react"; -import "@xyflow/react/dist/style.css"; - -import type { - ChainWithContracts, - ChainGraphResponse, - ChainContractDefinition, - ChainDefinitionGraphResponse, - AddContractDefinitionRequest, - ChainRepository, - AddChainRepositoryRequest, -} from "../../lib/api"; -import { - listChainDefinitions, - createChainDefinition, - updateChainDefinition, - deleteChainDefinition, - getChainDefinitionGraph, - startChain, - stopChain, - listChainRepositories, - addChainRepository, - deleteChainRepository, - setChainRepositoryPrimary, -} from "../../lib/api"; - -const statusColors: Record = { - active: "text-green-400", - completed: "text-blue-400", - archived: "text-[#555]", - pending: "text-yellow-400", -}; - -interface ChainEditorProps { - chain: ChainWithContracts; - graph: ChainGraphResponse | null; - loading: boolean; - onBack: () => void; - onRefresh: () => void; - onContractClick: (contractId: string) => void; -} - -// Node dimensions for layout -const NODE_WIDTH = 200; -const NODE_HEIGHT = 80; -const GRID_SPACING_X = 280; -const GRID_SPACING_Y = 120; - -// Custom node component for definitions -function DefinitionNodeComponent({ - data, - selected, -}: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any; - selected?: boolean; -}) { - const isCheckpoint = data.contractType === "checkpoint"; - const status = data.isInstantiated ? data.contractStatus || "pending" : "pending"; - const colors = getStatusColor(status, isCheckpoint); - - return ( -
- {/* Top handle for incoming edges */} - - - {/* Status indicator bar */} -
- - {/* Content */} -
-
- - {data.name} - - {isCheckpoint ? ( - - ) : ( - - )} -
-
- - {data.isInstantiated ? status : isCheckpoint ? "checkpoint" : "definition"} - - - {data.contractType} - -
-
- - {/* Bottom handle for outgoing edges */} - -
- ); -} - -// Custom node for contracts (active chains) -function ContractNodeComponent({ - data, - selected, -}: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any; - selected?: boolean; -}) { - const colors = getStatusColor(data.status); - - return ( -
- - -
- -
-
- - {data.name} - - -
-
- - {data.status} - - {data.phase && ( - {data.phase} - )} -
-
- - -
- ); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const nodeTypes: Record = { - definition: DefinitionNodeComponent, - contract: ContractNodeComponent, -}; - -function getStatusColor(status: string, isCheckpoint = false) { - if (isCheckpoint) { - switch (status) { - case "active": - return { bg: "#a78bfa", border: "#8b5cf6", text: "#5b21b6" }; - case "completed": - return { bg: "#818cf8", border: "#6366f1", text: "#3730a3" }; - case "pending": - return { bg: "#c4b5fd", border: "#a78bfa", text: "#6d28d9" }; - case "failed": - return { bg: "#ef4444", border: "#dc2626", text: "#991b1b" }; - default: - return { bg: "#a78bfa", border: "#8b5cf6", text: "#5b21b6" }; - } - } - switch (status) { - case "active": - return { bg: "#4ade80", border: "#22c55e", text: "#166534" }; - case "completed": - return { bg: "#60a5fa", border: "#3b82f6", text: "#1e40af" }; - case "pending": - return { bg: "#f59e0b", border: "#d97706", text: "#92400e" }; - case "blocked": - return { bg: "#ef4444", border: "#dc2626", text: "#991b1b" }; - default: - return { bg: "#6b7280", border: "#4b5563", text: "#374151" }; - } -} - -export function ChainEditor({ - chain, - graph, - loading, - onBack, - onRefresh, - onContractClick, -}: ChainEditorProps) { - const [definitions, setDefinitions] = useState([]); - const [definitionGraph, setDefinitionGraph] = useState(null); - const [showAddDefinition, setShowAddDefinition] = useState(false); - const [isStarting, setIsStarting] = useState(false); - const [isStopping, setIsStopping] = useState(false); - const [error, setError] = useState(null); - const [selectedNodeId, setSelectedNodeId] = useState(null); - const [repositories, setRepositories] = useState([]); - const [showAddRepo, setShowAddRepo] = useState(false); - - const [nodes, setNodes, onNodesChange] = useNodesState([] as Node[]); - const [edges, setEdges, onEdgesChange] = useEdgesState([] as Edge[]); - - const showDefinitions = chain.status === "pending" || chain.status === "archived"; - const canEdit = chain.status === "pending"; - - // Load definitions and repositories when chain changes - useEffect(() => { - async function loadData() { - try { - const [defs, defGraph, repos] = await Promise.all([ - listChainDefinitions(chain.id), - getChainDefinitionGraph(chain.id), - listChainRepositories(chain.id), - ]); - setDefinitions(defs); - setDefinitionGraph(defGraph); - setRepositories(repos); - } catch (err) { - console.error("Failed to load data:", err); - } - } - loadData(); - }, [chain.id]); - - // Convert definitions/contracts to React Flow nodes and edges - useEffect(() => { - if (showDefinitions && definitionGraph) { - const flowNodes: Node[] = definitionGraph.nodes.map((node) => ({ - id: node.id, - type: "definition", - position: { - x: (node.x || 0) * GRID_SPACING_X, - y: (node.y || 0) * GRID_SPACING_Y, - }, - data: { - name: node.name, - contractType: node.contractType, - isInstantiated: node.isInstantiated, - contractStatus: node.contractStatus, - }, - draggable: canEdit, - })); - - const flowEdges: Edge[] = definitionGraph.edges.map((edge, index) => ({ - id: `${edge.from}-${edge.to}-${index}`, - source: edge.from, - target: edge.to, - type: "smoothstep", - animated: false, - style: { stroke: "#3f6fb3", strokeWidth: 2 }, - markerEnd: { type: MarkerType.ArrowClosed, color: "#75aafc" }, - })); - - setNodes(flowNodes); - setEdges(flowEdges); - } else if (!showDefinitions && graph) { - const flowNodes: Node[] = graph.nodes.map((node) => ({ - id: node.contractId, - type: "contract", - position: { - x: (node.x || 0) * GRID_SPACING_X, - y: (node.y || 0) * GRID_SPACING_Y, - }, - data: { - name: node.name, - status: node.status, - phase: node.phase, - }, - draggable: false, - })); - - const flowEdges: Edge[] = graph.edges.map((edge, index) => ({ - id: `${edge.from}-${edge.to}-${index}`, - source: edge.from, - target: edge.to, - type: "smoothstep", - animated: edge.from === selectedNodeId || edge.to === selectedNodeId, - style: { stroke: "#3f6fb3", strokeWidth: 2 }, - markerEnd: { type: MarkerType.ArrowClosed, color: "#75aafc" }, - })); - - setNodes(flowNodes); - setEdges(flowEdges); - } - }, [showDefinitions, definitionGraph, graph, canEdit, selectedNodeId, setNodes, setEdges]); - - // Handle node position changes (drag end) - const handleNodesChange = useCallback( - async (changes: NodeChange[]) => { - onNodesChange(changes); - - // Save position changes to backend - for (const change of changes) { - if (change.type === "position" && change.dragging === false && change.position) { - const gridX = Math.round(change.position.x / GRID_SPACING_X); - const gridY = Math.round(change.position.y / GRID_SPACING_Y); - - try { - await updateChainDefinition(chain.id, change.id, { - editorX: gridX, - editorY: gridY, - }); - } catch (err) { - console.error("Failed to save position:", err); - } - } - } - }, - [chain.id, onNodesChange] - ); - - // Handle new edge connections - const handleConnect = useCallback( - async (connection: Connection) => { - if (!connection.source || !connection.target) return; - - // Find the definitions - const sourceDef = definitions.find((d) => d.id === connection.source); - const targetDef = definitions.find((d) => d.id === connection.target); - - if (!sourceDef || !targetDef) return; - - // Add dependency: target depends on source - const currentDeps = targetDef.dependsOnNames || []; - if (!currentDeps.includes(sourceDef.name)) { - try { - await updateChainDefinition(chain.id, connection.target, { - dependsOn: [...currentDeps, sourceDef.name], - }); - - // Refresh - const [defs, defGraph] = await Promise.all([ - listChainDefinitions(chain.id), - getChainDefinitionGraph(chain.id), - ]); - setDefinitions(defs); - setDefinitionGraph(defGraph); - } catch (err) { - console.error("Failed to create dependency:", err); - setError(err instanceof Error ? err.message : "Failed to create dependency"); - } - } - }, - [chain.id, definitions] - ); - - // Handle node selection - const handleNodeClick = useCallback( - (_: React.MouseEvent, node: Node) => { - setSelectedNodeId(node.id); - }, - [] - ); - - // Handle node double-click (open contract) - const handleNodeDoubleClick = useCallback( - (_: React.MouseEvent, node: Node) => { - if (!showDefinitions) { - onContractClick(node.id); - } - }, - [showDefinitions, onContractClick] - ); - - // Handle pane click (deselect) - const handlePaneClick = useCallback(() => { - setSelectedNodeId(null); - }, []); - - // Find selected definition - const selectedDefinition = showDefinitions && selectedNodeId - ? definitions.find((d) => d.id === selectedNodeId) - : null; - - // Find free position for new definition - const findFreePosition = useCallback(() => { - if (!definitionGraph?.nodes || definitionGraph.nodes.length === 0) { - return { x: 0, y: 0 }; - } - - const occupied = new Set(); - for (const node of definitionGraph.nodes) { - occupied.add(`${node.x},${node.y}`); - } - - for (let y = 0; y < 10; y++) { - for (let x = 0; x < 10; x++) { - if (!occupied.has(`${x},${y}`)) { - return { x, y }; - } - } - } - - const maxY = Math.max(...definitionGraph.nodes.map((n) => n.y || 0)); - return { x: 0, y: maxY + 1 }; - }, [definitionGraph?.nodes]); - - const handleAddDefinition = useCallback( - async (req: AddContractDefinitionRequest) => { - try { - const position = findFreePosition(); - const reqWithPosition = { ...req, editorX: position.x, editorY: position.y }; - await createChainDefinition(chain.id, reqWithPosition); - - const [defs, defGraph] = await Promise.all([ - listChainDefinitions(chain.id), - getChainDefinitionGraph(chain.id), - ]); - setDefinitions(defs); - setDefinitionGraph(defGraph); - setShowAddDefinition(false); - } catch (err) { - console.error("Failed to add definition:", err); - setError(err instanceof Error ? err.message : "Failed to add definition"); - } - }, - [chain.id, findFreePosition] - ); - - const handleDeleteDefinition = useCallback( - async (definitionId: string) => { - if (!confirm("Are you sure you want to delete this definition?")) return; - try { - await deleteChainDefinition(chain.id, definitionId); - - const [defs, defGraph] = await Promise.all([ - listChainDefinitions(chain.id), - getChainDefinitionGraph(chain.id), - ]); - setDefinitions(defs); - setDefinitionGraph(defGraph); - setSelectedNodeId(null); - } catch (err) { - console.error("Failed to delete definition:", err); - setError(err instanceof Error ? err.message : "Failed to delete definition"); - } - }, - [chain.id] - ); - - const handleStartChain = useCallback(async () => { - setIsStarting(true); - setError(null); - try { - await startChain(chain.id); - onRefresh(); - } catch (err) { - setError(err instanceof Error ? err.message : "Failed to start chain"); - } finally { - setIsStarting(false); - } - }, [chain.id, onRefresh]); - - const handleStopChain = useCallback(async () => { - if (!confirm("Are you sure you want to stop this chain?")) return; - setIsStopping(true); - setError(null); - try { - await stopChain(chain.id); - onRefresh(); - } catch (err) { - setError(err instanceof Error ? err.message : "Failed to stop chain"); - } finally { - setIsStopping(false); - } - }, [chain.id, onRefresh]); - - const handleRemoveDependency = useCallback( - async (nodeId: string, depName: string) => { - const def = definitions.find((d) => d.id === nodeId); - if (!def) return; - - const newDeps = (def.dependsOnNames || []).filter((d) => d !== depName); - try { - await updateChainDefinition(chain.id, nodeId, { dependsOn: newDeps }); - - const [defs, defGraph] = await Promise.all([ - listChainDefinitions(chain.id), - getChainDefinitionGraph(chain.id), - ]); - setDefinitions(defs); - setDefinitionGraph(defGraph); - } catch (err) { - console.error("Failed to remove dependency:", err); - setError(err instanceof Error ? err.message : "Failed to remove dependency"); - } - }, - [chain.id, definitions] - ); - - // Repository handlers - const handleAddRepository = useCallback( - async (req: AddChainRepositoryRequest) => { - try { - await addChainRepository(chain.id, req); - const repos = await listChainRepositories(chain.id); - setRepositories(repos); - setShowAddRepo(false); - } catch (err) { - console.error("Failed to add repository:", err); - setError(err instanceof Error ? err.message : "Failed to add repository"); - } - }, - [chain.id] - ); - - const handleDeleteRepository = useCallback( - async (repoId: string) => { - if (!confirm("Remove this repository from the chain?")) return; - try { - await deleteChainRepository(chain.id, repoId); - const repos = await listChainRepositories(chain.id); - setRepositories(repos); - } catch (err) { - console.error("Failed to delete repository:", err); - setError(err instanceof Error ? err.message : "Failed to delete repository"); - } - }, - [chain.id] - ); - - const handleSetPrimary = useCallback( - async (repoId: string) => { - try { - await setChainRepositoryPrimary(chain.id, repoId); - const repos = await listChainRepositories(chain.id); - setRepositories(repos); - } catch (err) { - console.error("Failed to set primary:", err); - setError(err instanceof Error ? err.message : "Failed to set primary repository"); - } - }, - [chain.id] - ); - - return ( -
- {/* Header */} -
-
-
- -
-

{chain.name}

- {chain.description && ( -

{chain.description}

- )} -
-
-
- - {chain.status} - - {chain.status === "pending" && definitions.length > 0 && ( - - )} - {chain.status === "active" && ( - - )} - -
-
- {error && ( -
- {error} -
- )} -
- - {/* Repository section */} -
-
- - Repositories ({repositories.length}) - - {canEdit && ( - - )} -
- {repositories.length > 0 && ( -
- {repositories.map((repo) => ( -
- - - {repo.name} - - {repo.isPrimary && ( - primary - )} - {canEdit && !repo.isPrimary && ( - - )} - {canEdit && ( - - )} -
- ))} -
- )} - {repositories.length === 0 && ( -

- No repositories attached. {canEdit && "Add one to use with contracts."} -

- )} -
- - {/* Main content */} -
- {/* React Flow Canvas */} -
- {loading ? ( -
-

Loading graph...

-
- ) : nodes.length === 0 ? ( -
-
-

- {showDefinitions - ? "No contract definitions yet" - : "No contracts in this chain yet"} -

-

- {showDefinitions - ? "Add contract definitions to build your chain" - : "Start the chain to create contracts from definitions"} -

- {showDefinitions && canEdit && ( - - )} -
-
- ) : ( - - - - - )} -
- - {/* Detail panel */} - {selectedDefinition && ( - setSelectedNodeId(null)} - onDelete={handleDeleteDefinition} - onRemoveDependency={handleRemoveDependency} - /> - )} -
- - {/* Footer with stats */} -
-
- {showDefinitions ? ( - <> - {definitions.length} definitions - {canEdit && ( - <> - | - Drag nodes to reposition - | - - Drag from to link - - - )} - - {canEdit && ( - - )} - - ) : ( - <> - {chain.contracts.length} contracts - - {chain.contracts.filter((c) => c.contractStatus === "completed").length}{" "} - completed - - - {chain.contracts.filter((c) => c.contractStatus === "active").length} active - - - Double-click node to open contract - - )} -
-
- - {/* Add Definition Modal */} - {showAddDefinition && ( - d.name)} - onSubmit={handleAddDefinition} - onCancel={() => setShowAddDefinition(false)} - /> - )} - - {/* Add Repository Modal */} - {showAddRepo && ( - setShowAddRepo(false)} - /> - )} -
- ); -} - -// Detail panel for definitions -interface DefinitionDetailPanelProps { - definition: ChainContractDefinition; - onClose: () => void; - onDelete: (id: string) => void; - onRemoveDependency: (nodeId: string, depName: string) => void; -} - -function DefinitionDetailPanel({ - definition, - onClose, - onDelete, - onRemoveDependency, -}: DefinitionDetailPanelProps) { - const dependencies = definition.dependsOnNames || []; - - return ( -
-
-
-

Definition Details

- -
-
- -
- {/* Name */} -
- -

{definition.name}

-
- - {/* Description */} - {definition.description && ( -
- -

{definition.description}

-
- )} - - {/* Type */} -
- -

{definition.contractType}

-
- - {/* Dependencies */} - {dependencies.length > 0 && ( -
- -
- {dependencies.map((dep) => ( -
- {dep} - -
- ))} -
-
- )} - - {/* Delete button */} -
- -
-
-
- ); -} - -// Add Definition Modal -interface AddDefinitionModalProps { - existingNames: string[]; - onSubmit: (req: AddContractDefinitionRequest) => void; - onCancel: () => void; -} - -function AddDefinitionModal({ existingNames, onSubmit, onCancel }: AddDefinitionModalProps) { - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [contractType, setContractType] = useState("simple"); - const [initialPhase, setInitialPhase] = useState("plan"); - const [dependsOn, setDependsOn] = useState([]); - - const isCheckpoint = contractType === "checkpoint"; - - const handleSubmit = () => { - if (!name.trim()) return; - const req: AddContractDefinitionRequest = { - name: name.trim(), - description: description.trim() || undefined, - contractType, - initialPhase: isCheckpoint ? "execute" : initialPhase, - dependsOn: dependsOn.length > 0 ? dependsOn : undefined, - }; - onSubmit(req); - }; - - const toggleDependency = (depName: string) => { - setDependsOn((prev) => - prev.includes(depName) ? prev.filter((d) => d !== depName) : [...prev, depName] - ); - }; - - return ( -
-
-

- Add Contract Definition -

- -
- {/* Name */} -
- - setName(e.target.value)} - className="w-full px-3 py-2 bg-[#050d18] border border-[rgba(117,170,252,0.3)] font-mono text-sm text-[#dbe7ff] focus:border-[#75aafc] focus:outline-none" - placeholder="e.g., Research Phase" - /> -
- - {/* Description */} -
- -