From 97e21c8296ec5f91912d56980ebf3b18a1ca3507 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sat, 7 Feb 2026 18:27:54 +0000
Subject: Add directive monitor contracts

---
 .../src/components/directives/DirectiveDetail.tsx  | 393 +++++++++++++++------
 1 file changed, 288 insertions(+), 105 deletions(-)

(limited to 'makima/frontend/src/components/directives/DirectiveDetail.tsx')

diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
index 094cdf2..95dc7cc 100644
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx
@@ -1,12 +1,16 @@
-import { useEffect, useRef } from "react";
+import { useState, useEffect, useRef } from "react";
 import { useNavigate } from "react-router";
 import type {
   DirectiveWithChains,
   DirectiveStatus,
   ChainWithSteps,
   ChainStep,
+  ContractPhase,
 } from "../../lib/api";
 import { getDirective } from "../../lib/api";
+import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
+import { StepDiagram } from "./StepDiagram";
+import { DirectiveContractsTab } from "./DirectiveContractsTab";
 
 interface DirectiveDetailProps {
   directive: DirectiveWithChains;
@@ -16,6 +20,8 @@ interface DirectiveDetailProps {
   onRefresh?: (updated: DirectiveWithChains) => void;
 }
 
+type Tab = "overview" | "chain" | "contracts";
+
 const statusColors: Record<DirectiveStatus, string> = {
   draft: "text-[#888]",
   planning: "text-yellow-400",
@@ -36,17 +42,18 @@ const stepStatusColors: Record<string, string> = {
 const stepStatusIcons: Record<string, string> = {
   pending: "\u25CB", // ○
   running: "\u25D4", // ◔
-  passed: "\u25CF",  // ●
-  failed: "\u2715",  // ✕
+  passed: "\u25CF", // ●
+  failed: "\u2715", // ✕
 };
 
 function StepRow({ step }: { step: ChainStep }) {
   const navigate = useNavigate();
   const color = stepStatusColors[step.status] || "text-[#888]";
   const icon = stepStatusIcons[step.status] || "\u25CB";
+  const summary = step.contractSummary;
 
   return (
-    <div className="flex items-start gap-2 py-1 px-2 hover:bg-[rgba(117,170,252,0.05)]">
+    <div className="flex items-start gap-2 py-1.5 px-2 hover:bg-[rgba(117,170,252,0.05)]">
       <span className={`font-mono text-[11px] ${color} mt-px`}>{icon}</span>
       <div className="flex-1 min-w-0">
         <div className="flex items-center gap-2">
@@ -57,21 +64,41 @@ function StepRow({ step }: { step: ChainStep }) {
             {step.status}
           </span>
         </div>
+        {summary && (
+          <div className="flex items-center gap-2 mt-0.5">
+            <PhaseProgressBarCompact
+              currentPhase={summary.phase as ContractPhase}
+            />
+            <span className="font-mono text-[9px] text-[#7788aa]">
+              {summary.tasksDone}/{summary.taskCount} tasks
+            </span>
+            {step.contractId && (
+              <button
+                onClick={(e) => {
+                  e.stopPropagation();
+                  navigate(`/contracts/${step.contractId}`);
+                }}
+                className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors"
+              >
+                contract &rarr;
+              </button>
+            )}
+          </div>
+        )}
+        {!summary && step.contractId && (
+          <button
+            onClick={() => navigate(`/contracts/${step.contractId}`)}
+            className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors mt-0.5"
+          >
+            contract &rarr;
+          </button>
+        )}
         {step.description && (
-          <p className="font-mono text-[10px] text-[#7788aa] truncate">
+          <p className="font-mono text-[10px] text-[#7788aa] truncate mt-0.5">
             {step.description}
           </p>
         )}
       </div>
-      {step.contractId && (
-        <button
-          onClick={() => navigate(`/contracts/${step.contractId}`)}
-          className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors shrink-0"
-          title="View contract"
-        >
-          contract &rarr;
-        </button>
-      )}
     </div>
   );
 }
@@ -84,7 +111,9 @@ function ChainCard({ chainWithSteps }: { chainWithSteps: ChainWithSteps }) {
     <div className="border border-dashed border-[rgba(117,170,252,0.25)] bg-[rgba(117,170,252,0.03)]">
       <div className="p-3">
         <div className="flex items-center justify-between mb-1">
-          <span className="font-mono text-xs text-[#dbe7ff]">{chain.name}</span>
+          <span className="font-mono text-xs text-[#dbe7ff]">
+            {chain.name}
+          </span>
           <span className="font-mono text-[10px] text-[#7788aa] uppercase">
             gen {chain.generation} &middot; {chain.status}
           </span>
@@ -102,7 +131,9 @@ function ChainCard({ chainWithSteps }: { chainWithSteps: ChainWithSteps }) {
             <span className="text-red-400">{chain.failedSteps} failed</span>
           )}
           {chain.currentConfidence != null && (
-            <span>confidence: {(chain.currentConfidence * 100).toFixed(0)}%</span>
+            <span>
+              confidence: {(chain.currentConfidence * 100).toFixed(0)}%
+            </span>
           )}
         </div>
       </div>
@@ -151,6 +182,7 @@ export function DirectiveDetail({
   onRefresh,
 }: DirectiveDetailProps) {
   const navigate = useNavigate();
+  const [activeTab, setActiveTab] = useState<Tab>("overview");
 
   // Auto-poll when directive is in an active state
   const isLive =
@@ -185,6 +217,31 @@ export function DirectiveDetail({
     };
   }, [isLive, directive.id, onRefresh]);
 
+  // Count total steps and completed steps across all chains
+  const totalSteps = directive.chains.reduce(
+    (sum, c) => sum + c.totalSteps,
+    0
+  );
+  const completedSteps = directive.chains.reduce(
+    (sum, c) => sum + c.completedSteps,
+    0
+  );
+
+  // Count contracts
+  const contractCount =
+    (directive.orchestratorContractSummary ? 1 : 0) +
+    directive.chains.reduce(
+      (sum, c) =>
+        sum + c.steps.filter((s) => s.contractSummary != null).length,
+      0
+    );
+
+  const tabs: { key: Tab; label: string; count?: number }[] = [
+    { key: "overview", label: "Overview" },
+    { key: "chain", label: "Chain", count: totalSteps },
+    { key: "contracts", label: "Contracts", count: contractCount },
+  ];
+
   return (
     <div className="panel h-full flex flex-col">
       {/* Header */}
@@ -235,109 +292,235 @@ export function DirectiveDetail({
         </h2>
       </div>
 
-      {/* Content */}
-      <div className="flex-1 overflow-y-auto p-4 space-y-4">
-        {/* Orchestrator contract link */}
-        {directive.orchestratorContractId && (
-          <div className="flex items-center gap-2 p-2 border border-dashed border-[rgba(117,170,252,0.2)] bg-[rgba(117,170,252,0.03)]">
-            <span className="font-mono text-[10px] text-[#7788aa] uppercase">
-              Planning Contract
-            </span>
-            <button
-              onClick={() =>
-                navigate(`/contracts/${directive.orchestratorContractId}`)
+      {/* Tabs */}
+      <div className="flex border-b border-[rgba(117,170,252,0.2)]">
+        {tabs.map((tab) => (
+          <button
+            key={tab.key}
+            onClick={() => setActiveTab(tab.key)}
+            className={`
+              px-4 py-2 font-mono text-xs uppercase tracking-wider transition-colors
+              ${
+                activeTab === tab.key
+                  ? "text-[#dbe7ff] border-b-2 border-[#75aafc]"
+                  : "text-[#555] hover:text-[#9bc3ff]"
               }
-              className="font-mono text-[11px] text-[#75aafc] hover:text-white transition-colors"
-            >
-              {directive.orchestratorContractId.slice(0, 8)}... &rarr;
-            </button>
-            {directive.status === "planning" && (
-              <span className="font-mono text-[9px] text-yellow-400 animate-pulse">
-                planning in progress
+            `}
+          >
+            {tab.label}
+            {tab.count != null && tab.count > 0 && (
+              <span className="ml-1 text-[10px] text-[#7788aa]">
+                ({tab.count})
               </span>
             )}
-          </div>
-        )}
+          </button>
+        ))}
+      </div>
 
-        {/* Goal */}
-        <div>
-          <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
-            Goal
-          </h4>
-          <p className="font-mono text-xs text-[#9bb8d8] whitespace-pre-wrap">
-            {directive.goal}
-          </p>
-        </div>
+      {/* Tab content */}
+      <div className="flex-1 overflow-y-auto p-4">
+        {activeTab === "overview" && (
+          <div className="space-y-4">
+            {/* Orchestrator contract link */}
+            {directive.orchestratorContractId && (
+              <div className="flex items-center gap-2 p-2 border border-dashed border-[rgba(117,170,252,0.2)] bg-[rgba(117,170,252,0.03)]">
+                <span className="font-mono text-[10px] text-[#7788aa] uppercase">
+                  Planning Contract
+                </span>
+                {directive.orchestratorContractSummary && (
+                  <PhaseProgressBarCompact
+                    currentPhase={
+                      directive.orchestratorContractSummary
+                        .phase as ContractPhase
+                    }
+                  />
+                )}
+                <button
+                  onClick={() =>
+                    navigate(
+                      `/contracts/${directive.orchestratorContractId}`
+                    )
+                  }
+                  className="font-mono text-[11px] text-[#75aafc] hover:text-white transition-colors"
+                >
+                  {directive.orchestratorContractSummary?.name ||
+                    directive.orchestratorContractId.slice(0, 8) + "..."}{" "}
+                  &rarr;
+                </button>
+                {directive.status === "planning" && (
+                  <span className="font-mono text-[9px] text-yellow-400 animate-pulse">
+                    planning in progress
+                  </span>
+                )}
+              </div>
+            )}
 
-        {/* Config */}
-        <div className="grid grid-cols-2 gap-2">
-          <div>
-            <span className="font-mono text-[10px] text-[#7788aa] uppercase">
-              Autonomy
-            </span>
-            <div className="font-mono text-xs text-[#dbe7ff]">
-              {directive.autonomyLevel}
+            {/* Goal */}
+            <div>
+              <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
+                Goal
+              </h4>
+              <p className="font-mono text-xs text-[#9bb8d8] whitespace-pre-wrap">
+                {directive.goal}
+              </p>
             </div>
-          </div>
-          <div>
-            <span className="font-mono text-[10px] text-[#7788aa] uppercase">
-              Chains
-            </span>
-            <div className="font-mono text-xs text-[#dbe7ff]">
-              {directive.chainGenerationCount} generated
+
+            {/* Config grid */}
+            <div className="grid grid-cols-3 gap-2">
+              <div>
+                <span className="font-mono text-[10px] text-[#7788aa] uppercase">
+                  Autonomy
+                </span>
+                <div className="font-mono text-xs text-[#dbe7ff]">
+                  {directive.autonomyLevel}
+                </div>
+              </div>
+              <div>
+                <span className="font-mono text-[10px] text-[#7788aa] uppercase">
+                  Chains
+                </span>
+                <div className="font-mono text-xs text-[#dbe7ff]">
+                  {directive.chainGenerationCount} generated
+                </div>
+              </div>
+              <div>
+                <span className="font-mono text-[10px] text-[#7788aa] uppercase">
+                  Cost
+                </span>
+                <div className="font-mono text-xs text-[#dbe7ff]">
+                  ${directive.totalCostUsd.toFixed(2)}
+                </div>
+              </div>
+              {directive.repositoryUrl && (
+                <div className="col-span-3">
+                  <span className="font-mono text-[10px] text-[#7788aa] uppercase">
+                    Repository
+                  </span>
+                  <div className="font-mono text-xs text-[#dbe7ff] truncate">
+                    {directive.repositoryUrl}
+                  </div>
+                </div>
+              )}
             </div>
-          </div>
-          <div>
-            <span className="font-mono text-[10px] text-[#7788aa] uppercase">
-              Cost
-            </span>
-            <div className="font-mono text-xs text-[#dbe7ff]">
-              ${directive.totalCostUsd.toFixed(2)}
+
+            {/* Stat cards */}
+            <div className="grid grid-cols-3 gap-2">
+              <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
+                <div className="font-mono text-lg text-[#dbe7ff]">
+                  {totalSteps}
+                </div>
+                <div className="font-mono text-[9px] text-[#7788aa] uppercase">
+                  Total Steps
+                </div>
+              </div>
+              <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
+                <div className="font-mono text-lg text-green-400">
+                  {completedSteps}
+                </div>
+                <div className="font-mono text-[9px] text-[#7788aa] uppercase">
+                  Completed
+                </div>
+              </div>
+              <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
+                <div className="font-mono text-lg text-[#dbe7ff]">
+                  ${directive.totalCostUsd.toFixed(2)}
+                </div>
+                <div className="font-mono text-[9px] text-[#7788aa] uppercase">
+                  Cost
+                </div>
+              </div>
             </div>
-          </div>
-          {directive.repositoryUrl && (
+
+            {/* Structured sections */}
+            <JsonSection label="Requirements" data={directive.requirements} />
+            <JsonSection
+              label="Acceptance Criteria"
+              data={directive.acceptanceCriteria}
+            />
+            <JsonSection label="Constraints" data={directive.constraints} />
+            <JsonSection
+              label="External Dependencies"
+              data={directive.externalDependencies}
+            />
+
+            {/* Metadata */}
             <div>
-              <span className="font-mono text-[10px] text-[#7788aa] uppercase">
-                Repository
-              </span>
-              <div className="font-mono text-xs text-[#dbe7ff] truncate">
-                {directive.repositoryUrl}
+              <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
+                Metadata
+              </h4>
+              <div className="grid grid-cols-2 gap-1 font-mono text-[10px]">
+                <span className="text-[#7788aa]">Created</span>
+                <span className="text-[#9bb8d8]">
+                  {new Date(directive.createdAt).toLocaleString()}
+                </span>
+                <span className="text-[#7788aa]">Updated</span>
+                <span className="text-[#9bb8d8]">
+                  {new Date(directive.updatedAt).toLocaleString()}
+                </span>
+                {directive.startedAt && (
+                  <>
+                    <span className="text-[#7788aa]">Started</span>
+                    <span className="text-[#9bb8d8]">
+                      {new Date(directive.startedAt).toLocaleString()}
+                    </span>
+                  </>
+                )}
+                {directive.completedAt && (
+                  <>
+                    <span className="text-[#7788aa]">Completed</span>
+                    <span className="text-[#9bb8d8]">
+                      {new Date(directive.completedAt).toLocaleString()}
+                    </span>
+                  </>
+                )}
+                <span className="text-[#7788aa]">Version</span>
+                <span className="text-[#9bb8d8]">{directive.version}</span>
               </div>
             </div>
-          )}
-        </div>
+          </div>
+        )}
 
-        {/* Structured sections */}
-        <JsonSection label="Requirements" data={directive.requirements} />
-        <JsonSection
-          label="Acceptance Criteria"
-          data={directive.acceptanceCriteria}
-        />
-        <JsonSection label="Constraints" data={directive.constraints} />
-        <JsonSection
-          label="External Dependencies"
-          data={directive.externalDependencies}
-        />
+        {activeTab === "chain" && (
+          <div className="space-y-4">
+            {/* Step diagram */}
+            {directive.chains.length > 0 && (
+              <div>
+                <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2">
+                  Step Dependencies
+                </h4>
+                <StepDiagram
+                  steps={directive.chains.flatMap((c) => c.steps)}
+                />
+              </div>
+            )}
 
-        {/* Chains */}
-        <div>
-          <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2">
-            Chains ({directive.chains.length})
-          </h4>
-          {directive.chains.length === 0 ? (
-            <p className="font-mono text-xs text-[#7788aa]">
-              {directive.status === "planning"
-                ? "Planning in progress... chains will appear when the planner completes."
-                : "No chains yet. Chains are created during planning."}
-            </p>
-          ) : (
-            <div className="space-y-2">
-              {directive.chains.map((cws) => (
-                <ChainCard key={cws.id} chainWithSteps={cws} />
-              ))}
+            {/* Chain cards */}
+            <div>
+              <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2">
+                Chains ({directive.chains.length})
+              </h4>
+              {directive.chains.length === 0 ? (
+                <p className="font-mono text-xs text-[#7788aa]">
+                  {directive.status === "planning"
+                    ? "Planning in progress... chains will appear when the planner completes."
+                    : directive.status === "draft"
+                      ? "No chains yet. Start the directive to begin planning."
+                      : "No chains created for this directive."}
+                </p>
+              ) : (
+                <div className="space-y-2">
+                  {directive.chains.map((cws) => (
+                    <ChainCard key={cws.id} chainWithSteps={cws} />
+                  ))}
+                </div>
+              )}
             </div>
-          )}
-        </div>
+          </div>
+        )}
+
+        {activeTab === "contracts" && (
+          <DirectiveContractsTab directive={directive} />
+        )}
       </div>
     </div>
   );
-- 
cgit v1.2.3

