From 87044a747b47bd83249d61a45842c7f7b2eae56d Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sun, 11 Jan 2026 05:52:14 +0000
Subject:  Contract system

---
 .../contracts/PhaseDeliverablesPanel.tsx           | 301 +++++++++++++++++++++
 1 file changed, 301 insertions(+)
 create mode 100644 makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx

(limited to 'makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx')

diff --git a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
new file mode 100644
index 0000000..da5025b
--- /dev/null
+++ b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
@@ -0,0 +1,301 @@
+import { useMemo } from "react";
+import type { ContractWithRelations, ContractPhase } from "../../lib/api";
+
+// Phase deliverables configuration (mirrors backend phase_guidance.rs)
+interface RecommendedFile {
+  templateId: string;
+  name: string;
+  priority: "required" | "recommended" | "optional";
+  description: string;
+}
+
+interface PhaseDeliverables {
+  phase: ContractPhase;
+  files: RecommendedFile[];
+  requiresRepository: boolean;
+  requiresTasks: boolean;
+  guidance: string;
+}
+
+const PHASE_DELIVERABLES: Record<ContractPhase, PhaseDeliverables> = {
+  research: {
+    phase: "research",
+    files: [
+      { templateId: "research-notes", name: "Research Notes", priority: "recommended", description: "Document findings and insights" },
+      { templateId: "competitor-analysis", name: "Competitor Analysis", priority: "recommended", description: "Analyze competitors" },
+      { templateId: "user-research", name: "User Research", priority: "optional", description: "User interviews and personas" },
+    ],
+    requiresRepository: false,
+    requiresTasks: false,
+    guidance: "Gather information and document findings before moving to Specify.",
+  },
+  specify: {
+    phase: "specify",
+    files: [
+      { templateId: "requirements", name: "Requirements Document", priority: "required", description: "Functional and non-functional requirements" },
+      { templateId: "user-stories", name: "User Stories", priority: "recommended", description: "Features from user perspective" },
+      { templateId: "acceptance-criteria", name: "Acceptance Criteria", priority: "recommended", description: "Testable conditions for completion" },
+    ],
+    requiresRepository: false,
+    requiresTasks: false,
+    guidance: "Define clear requirements and acceptance criteria.",
+  },
+  plan: {
+    phase: "plan",
+    files: [
+      { templateId: "architecture", name: "Architecture Document", priority: "recommended", description: "System architecture and design" },
+      { templateId: "task-breakdown", name: "Task Breakdown", priority: "required", description: "Work broken into tasks" },
+      { templateId: "technical-design", name: "Technical Design", priority: "optional", description: "Detailed technical specs" },
+    ],
+    requiresRepository: true,
+    requiresTasks: false,
+    guidance: "Design the solution and create a task breakdown. Configure a repository.",
+  },
+  execute: {
+    phase: "execute",
+    files: [
+      { templateId: "dev-notes", name: "Development Notes", priority: "recommended", description: "Implementation details" },
+      { templateId: "test-plan", name: "Test Plan", priority: "optional", description: "Testing strategy" },
+      { templateId: "implementation-log", name: "Implementation Log", priority: "optional", description: "Progress log" },
+    ],
+    requiresRepository: true,
+    requiresTasks: true,
+    guidance: "Execute tasks and track implementation progress.",
+  },
+  review: {
+    phase: "review",
+    files: [
+      { templateId: "release-notes", name: "Release Notes", priority: "required", description: "Changes for release" },
+      { templateId: "review-checklist", name: "Review Checklist", priority: "recommended", description: "Code and feature review" },
+      { templateId: "retrospective", name: "Retrospective", priority: "optional", description: "Project learnings" },
+    ],
+    requiresRepository: false,
+    requiresTasks: false,
+    guidance: "Review work and document the release.",
+  },
+};
+
+interface DeliverableStatus {
+  templateId: string;
+  name: string;
+  priority: "required" | "recommended" | "optional";
+  description: string;
+  completed: boolean;
+  fileId?: string;
+  actualName?: string;
+}
+
+interface PhaseDeliverablesProps {
+  contract: ContractWithRelations;
+  onCreateFile?: (templateId: string, suggestedName: string) => void;
+}
+
+export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDeliverablesProps) {
+  const deliverables = PHASE_DELIVERABLES[contract.phase];
+
+  // Calculate deliverable status
+  const fileStatuses = useMemo((): DeliverableStatus[] => {
+    return deliverables.files.map((rec) => {
+      // Find matching file by name similarity
+      const matchedFile = contract.files.find((f) => {
+        const nameLower = f.name.toLowerCase();
+        const recLower = rec.name.toLowerCase();
+        return (
+          f.contractPhase === contract.phase &&
+          (nameLower.includes(recLower) || recLower.includes(nameLower) || nameLower.includes(rec.templateId.replace("-", " ")))
+        );
+      });
+
+      return {
+        ...rec,
+        completed: !!matchedFile,
+        fileId: matchedFile?.id,
+        actualName: matchedFile?.name,
+      };
+    });
+  }, [contract.files, contract.phase, deliverables.files]);
+
+  // Check repository status
+  const hasRepository = contract.repositories.length > 0;
+
+  // Check task status
+  const taskStats = useMemo(() => {
+    const total = contract.tasks.length;
+    const done = contract.tasks.filter((t) => t.status === "done" || t.status === "merged").length;
+    const pending = contract.tasks.filter((t) => t.status === "pending").length;
+    const running = contract.tasks.filter((t) => ["running", "initializing", "starting"].includes(t.status)).length;
+    const failed = contract.tasks.filter((t) => t.status === "failed").length;
+    return { total, done, pending, running, failed };
+  }, [contract.tasks]);
+
+  // Calculate completion percentage
+  const completionPercent = useMemo(() => {
+    let completed = 0;
+    let total = 0;
+
+    // Count required and recommended files
+    fileStatuses.forEach((s) => {
+      if (s.priority !== "optional") {
+        total++;
+        if (s.completed) completed++;
+      }
+    });
+
+    // Count repository if required
+    if (deliverables.requiresRepository) {
+      total++;
+      if (hasRepository) completed++;
+    }
+
+    // Count tasks if in execute phase
+    if (deliverables.requiresTasks && taskStats.total > 0) {
+      total++;
+      if (taskStats.done === taskStats.total) completed++;
+    }
+
+    return total > 0 ? Math.round((completed / total) * 100) : 100;
+  }, [fileStatuses, hasRepository, deliverables, taskStats]);
+
+  const priorityColors = {
+    required: "text-red-400",
+    recommended: "text-yellow-400",
+    optional: "text-[#555]",
+  };
+
+  return (
+    <div className="space-y-4">
+      <div className="flex items-center justify-between">
+        <h3 className="font-mono text-xs text-[#75aafc] uppercase">
+          Phase Deliverables
+        </h3>
+        <div className="flex items-center gap-2">
+          <div className="w-24 h-1.5 bg-[rgba(117,170,252,0.1)] rounded overflow-hidden">
+            <div
+              className={`h-full transition-all duration-300 ${
+                completionPercent === 100 ? "bg-green-400" : "bg-[#75aafc]"
+              }`}
+              style={{ width: `${completionPercent}%` }}
+            />
+          </div>
+          <span className="font-mono text-[10px] text-[#555]">{completionPercent}%</span>
+        </div>
+      </div>
+
+      {/* Guidance text */}
+      <p className="font-mono text-xs text-[#555] italic">{deliverables.guidance}</p>
+
+      {/* File deliverables */}
+      <div className="space-y-2">
+        {fileStatuses.map((status) => (
+          <div
+            key={status.templateId}
+            className={`flex items-center justify-between p-2 border ${
+              status.completed
+                ? "border-green-400/20 bg-green-400/5"
+                : "border-[rgba(117,170,252,0.15)]"
+            }`}
+          >
+            <div className="flex items-center gap-2">
+              <span
+                className={`font-mono text-xs ${
+                  status.completed ? "text-green-400" : "text-[#555]"
+                }`}
+              >
+                {status.completed ? "[+]" : "[ ]"}
+              </span>
+              <div>
+                <div className="flex items-center gap-2">
+                  <span className="font-mono text-xs text-[#dbe7ff]">
+                    {status.completed ? status.actualName : status.name}
+                  </span>
+                  {!status.completed && (
+                    <span className={`font-mono text-[9px] uppercase ${priorityColors[status.priority]}`}>
+                      {status.priority}
+                    </span>
+                  )}
+                </div>
+                <span className="font-mono text-[10px] text-[#555]">
+                  {status.description}
+                </span>
+              </div>
+            </div>
+            {!status.completed && onCreateFile && (
+              <button
+                onClick={() => onCreateFile(status.templateId, status.name)}
+                className="px-2 py-1 font-mono text-[10px] text-[#75aafc] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
+              >
+                Create
+              </button>
+            )}
+          </div>
+        ))}
+      </div>
+
+      {/* Repository status */}
+      {deliverables.requiresRepository && (
+        <div
+          className={`flex items-center gap-2 p-2 border ${
+            hasRepository
+              ? "border-green-400/20 bg-green-400/5"
+              : "border-[rgba(117,170,252,0.15)]"
+          }`}
+        >
+          <span
+            className={`font-mono text-xs ${
+              hasRepository ? "text-green-400" : "text-[#555]"
+            }`}
+          >
+            {hasRepository ? "[+]" : "[ ]"}
+          </span>
+          <div>
+            <span className="font-mono text-xs text-[#dbe7ff]">
+              Repository Configured
+            </span>
+            {!hasRepository && (
+              <span className="font-mono text-[9px] uppercase text-red-400 ml-2">
+                required
+              </span>
+            )}
+          </div>
+        </div>
+      )}
+
+      {/* Task status (execute phase) */}
+      {deliverables.requiresTasks && (
+        <div
+          className={`flex items-center justify-between p-2 border ${
+            taskStats.total > 0 && taskStats.done === taskStats.total
+              ? "border-green-400/20 bg-green-400/5"
+              : "border-[rgba(117,170,252,0.15)]"
+          }`}
+        >
+          <div className="flex items-center gap-2">
+            <span
+              className={`font-mono text-xs ${
+                taskStats.total > 0 && taskStats.done === taskStats.total
+                  ? "text-green-400"
+                  : "text-[#555]"
+              }`}
+            >
+              {taskStats.total > 0 && taskStats.done === taskStats.total ? "[+]" : "[ ]"}
+            </span>
+            <span className="font-mono text-xs text-[#dbe7ff]">
+              Tasks Completed
+            </span>
+          </div>
+          {taskStats.total > 0 ? (
+            <span className="font-mono text-[10px] text-[#9bc3ff]">
+              {taskStats.done}/{taskStats.total}
+              {taskStats.running > 0 && ` (${taskStats.running} running)`}
+              {taskStats.failed > 0 && (
+                <span className="text-red-400"> ({taskStats.failed} failed)</span>
+              )}
+            </span>
+          ) : (
+            <span className="font-mono text-[10px] text-[#555]">No tasks yet</span>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
-- 
cgit v1.2.3

