From a734bf1a472b19d63341769d26a66628575df7f4 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Wed, 4 Feb 2026 01:07:14 +0000
Subject: Add chain checkpoint contracts

---
 .../frontend/src/components/chains/ChainEditor.tsx | 207 +++++++++++++++++----
 1 file changed, 173 insertions(+), 34 deletions(-)

(limited to 'makima/frontend/src/components/chains/ChainEditor.tsx')

diff --git a/makima/frontend/src/components/chains/ChainEditor.tsx b/makima/frontend/src/components/chains/ChainEditor.tsx
index 9028c3e..49e585c 100644
--- a/makima/frontend/src/components/chains/ChainEditor.tsx
+++ b/makima/frontend/src/components/chains/ChainEditor.tsx
@@ -54,6 +54,7 @@ export function ChainEditor({
   const [isStarting, setIsStarting] = useState(false);
   const [isStopping, setIsStopping] = useState(false);
   const [error, setError] = useState<string | null>(null);
+  const [withSupervisor, setWithSupervisor] = useState(false);
 
   // Load definitions when chain changes
   useEffect(() => {
@@ -131,7 +132,22 @@ export function ChainEditor({
     [onContractClick, showDefinitions]
   );
 
-  const getStatusColor = (status: string) => {
+  const getStatusColor = (status: string, isCheckpoint = false) => {
+    // Checkpoint contracts use purple/violet colors
+    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" };
@@ -158,14 +174,14 @@ export function ChainEditor({
     setIsStarting(true);
     setError(null);
     try {
-      await startChain(chain.id);
+      await startChain(chain.id, { withSupervisor });
       onRefresh();
     } catch (err) {
       setError(err instanceof Error ? err.message : "Failed to start chain");
     } finally {
       setIsStarting(false);
     }
-  }, [chain.id, onRefresh]);
+  }, [chain.id, onRefresh, withSupervisor]);
 
   const handleStopChain = useCallback(async () => {
     if (!confirm("Are you sure you want to stop this chain?")) return;
@@ -245,13 +261,26 @@ export function ChainEditor({
             </span>
             {/* Chain control buttons */}
             {chain.status === "pending" && definitions.length > 0 && (
-              <button
-                onClick={handleStartChain}
-                disabled={isStarting}
-                className="px-3 py-1 font-mono text-xs text-[#dbe7ff] bg-green-600 hover:bg-green-700 border border-green-500 transition-colors disabled:opacity-50"
-              >
-                {isStarting ? "Starting..." : "Start Chain"}
-              </button>
+              <>
+                <label className="flex items-center gap-1 font-mono text-[10px] text-[#9bc3ff] cursor-pointer">
+                  <input
+                    type="checkbox"
+                    checked={withSupervisor}
+                    onChange={(e) => setWithSupervisor(e.target.checked)}
+                    className="w-3 h-3 accent-[#75aafc]"
+                  />
+                  <span title="Create a Claude supervisor task to monitor chain progress">
+                    Supervisor
+                  </span>
+                </label>
+                <button
+                  onClick={handleStartChain}
+                  disabled={isStarting}
+                  className="px-3 py-1 font-mono text-xs text-[#dbe7ff] bg-green-600 hover:bg-green-700 border border-green-500 transition-colors disabled:opacity-50"
+                >
+                  {isStarting ? "Starting..." : "Start Chain"}
+                </button>
+              </>
             )}
             {chain.status === "active" && (
               <button
@@ -381,10 +410,11 @@ export function ChainEditor({
                     const pos = nodePositions.get(node.id);
                     if (!pos) return null;
 
+                    const isCheckpoint = node.contractType === "checkpoint";
                     const status = node.isInstantiated
                       ? node.contractStatus || "pending"
                       : "pending";
-                    const colors = getStatusColor(status);
+                    const colors = getStatusColor(status, isCheckpoint);
                     const isSelected = selectedNode === node.id;
                     const isHovered = hoveredNode === node.id;
 
@@ -396,7 +426,9 @@ export function ChainEditor({
                         onMouseLeave={() => setHoveredNode(null)}
                         className={`absolute cursor-pointer transition-all duration-150 ${
                           isSelected
-                            ? "ring-2 ring-[#75aafc] ring-offset-2 ring-offset-[#050d18]"
+                            ? isCheckpoint
+                              ? "ring-2 ring-[#a78bfa] ring-offset-2 ring-offset-[#050d18]"
+                              : "ring-2 ring-[#75aafc] ring-offset-2 ring-offset-[#050d18]"
                             : ""
                         }`}
                         style={{
@@ -408,9 +440,15 @@ export function ChainEditor({
                         }}
                       >
                         <div
-                          className="w-full h-full rounded-lg border-2 bg-[#0a1628] overflow-hidden"
+                          className={`w-full h-full rounded-lg border-2 overflow-hidden ${
+                            isCheckpoint ? "bg-[#0f0a1e]" : "bg-[#0a1628]"
+                          }`}
                           style={{
-                            borderColor: isSelected ? "#75aafc" : colors.border,
+                            borderColor: isSelected
+                              ? isCheckpoint
+                                ? "#a78bfa"
+                                : "#75aafc"
+                              : colors.border,
                             borderStyle: node.isInstantiated ? "solid" : "dashed",
                           }}
                         >
@@ -425,7 +463,11 @@ export function ChainEditor({
                               <span className="font-mono text-xs text-[#dbe7ff] truncate flex-1">
                                 {node.name}
                               </span>
-                              <ChainIcon className="w-4 h-4 text-[#75aafc] opacity-50 flex-shrink-0 ml-1" />
+                              {isCheckpoint ? (
+                                <CheckIcon className="w-4 h-4 text-[#a78bfa] opacity-70 flex-shrink-0 ml-1" />
+                              ) : (
+                                <ChainIcon className="w-4 h-4 text-[#75aafc] opacity-50 flex-shrink-0 ml-1" />
+                              )}
                             </div>
                             <div className="flex items-center justify-between">
                               <span
@@ -435,7 +477,7 @@ export function ChainEditor({
                                   backgroundColor: `${colors.bg}20`,
                                 }}
                               >
-                                {node.isInstantiated ? status : "definition"}
+                                {node.isInstantiated ? status : isCheckpoint ? "checkpoint" : "definition"}
                               </span>
                               <span className="font-mono text-[10px] text-[#8b949e]">
                                 {node.contractType}
@@ -833,16 +875,33 @@ function AddDefinitionModal({
   const [contractType, setContractType] = useState("simple");
   const [initialPhase, setInitialPhase] = useState("plan");
   const [dependsOn, setDependsOn] = useState<string[]>([]);
+  // Checkpoint validation options
+  const [checkDeliverables, setCheckDeliverables] = useState(true);
+  const [runTests, setRunTests] = useState(false);
+  const [checkContent, setCheckContent] = useState("");
+  const [onFailure, setOnFailure] = useState<"block" | "retry" | "warn">("block");
+
+  const isCheckpoint = contractType === "checkpoint";
 
   const handleSubmit = () => {
     if (!name.trim()) return;
-    onSubmit({
+    const req: AddContractDefinitionRequest = {
       name: name.trim(),
       description: description.trim() || undefined,
       contractType,
-      initialPhase,
+      initialPhase: isCheckpoint ? "execute" : initialPhase, // Checkpoints always start in execute
       dependsOn: dependsOn.length > 0 ? dependsOn : undefined,
-    });
+    };
+    // Add validation config for checkpoint contracts
+    if (isCheckpoint) {
+      req.validation = {
+        checkDeliverables,
+        runTests,
+        checkContent: checkContent.trim() || undefined,
+        onFailure,
+      };
+    }
+    onSubmit(req);
   };
 
   const toggleDependency = (depName: string) => {
@@ -901,24 +960,87 @@ function AddDefinitionModal({
               <option value="simple">Simple</option>
               <option value="specification">Specification</option>
               <option value="execute">Execute</option>
+              <option value="checkpoint">Checkpoint (Validation)</option>
             </select>
+            {isCheckpoint && (
+              <p className="mt-1 font-mono text-[10px] text-[#a78bfa]">
+                Checkpoint contracts validate outputs before allowing downstream contracts to proceed.
+              </p>
+            )}
           </div>
 
-          {/* Initial Phase */}
-          <div>
-            <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
-              Initial Phase
-            </label>
-            <select
-              value={initialPhase}
-              onChange={(e) => setInitialPhase(e.target.value)}
-              className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
-            >
-              <option value="plan">Plan</option>
-              <option value="execute">Execute</option>
-              <option value="review">Review</option>
-            </select>
-          </div>
+          {/* Initial Phase - hidden for checkpoints */}
+          {!isCheckpoint && (
+            <div>
+              <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
+                Initial Phase
+              </label>
+              <select
+                value={initialPhase}
+                onChange={(e) => setInitialPhase(e.target.value)}
+                className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
+              >
+                <option value="plan">Plan</option>
+                <option value="execute">Execute</option>
+                <option value="review">Review</option>
+              </select>
+            </div>
+          )}
+
+          {/* Checkpoint Validation Options */}
+          {isCheckpoint && (
+            <div className="p-3 bg-[#0f0a1e] border border-[#6366f1]/30 space-y-3">
+              <h4 className="font-mono text-xs text-[#a78bfa] uppercase">Validation Options</h4>
+
+              <label className="flex items-center gap-2 cursor-pointer">
+                <input
+                  type="checkbox"
+                  checked={checkDeliverables}
+                  onChange={(e) => setCheckDeliverables(e.target.checked)}
+                  className="accent-[#a78bfa]"
+                />
+                <span className="font-mono text-xs text-[#dbe7ff]">Check required deliverables exist</span>
+              </label>
+
+              <label className="flex items-center gap-2 cursor-pointer">
+                <input
+                  type="checkbox"
+                  checked={runTests}
+                  onChange={(e) => setRunTests(e.target.checked)}
+                  className="accent-[#a78bfa]"
+                />
+                <span className="font-mono text-xs text-[#dbe7ff]">Run test suite</span>
+              </label>
+
+              <div>
+                <label className="block font-mono text-[10px] text-[#8b949e] uppercase mb-1">
+                  Custom Validation Instructions (optional)
+                </label>
+                <textarea
+                  value={checkContent}
+                  onChange={(e) => setCheckContent(e.target.value)}
+                  placeholder="Additional criteria for Claude to check..."
+                  rows={3}
+                  className="w-full px-2 py-1.5 bg-[#0d1b2d] border border-[#6366f1]/50 text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#a78bfa] resize-none"
+                />
+              </div>
+
+              <div>
+                <label className="block font-mono text-[10px] text-[#8b949e] uppercase mb-1">
+                  On Failure
+                </label>
+                <select
+                  value={onFailure}
+                  onChange={(e) => setOnFailure(e.target.value as "block" | "retry" | "warn")}
+                  className="w-full px-2 py-1.5 bg-[#0d1b2d] border border-[#6366f1]/50 text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#a78bfa]"
+                >
+                  <option value="block">Block - Stop chain until fixed</option>
+                  <option value="retry">Retry - Retry upstream contracts</option>
+                  <option value="warn">Warn - Log warning but continue</option>
+                </select>
+              </div>
+            </div>
+          )}
 
           {/* Dependencies */}
           {existingNames.length > 0 && (
@@ -980,3 +1102,20 @@ function ChainIcon({ className }: { className?: string }) {
     </svg>
   );
 }
+
+function CheckIcon({ className }: { className?: string }) {
+  return (
+    <svg
+      className={className}
+      viewBox="0 0 24 24"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    >
+      <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
+      <polyline points="22 4 12 14.01 9 11.01" />
+    </svg>
+  );
+}
-- 
cgit v1.2.3

