From 64cc98783d067625d633eea1142d114e324f76bb Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Mon, 26 Jan 2026 23:49:40 +0000
Subject: Use phase deliverables configured in contract types

---
 .../contracts/PhaseDeliverablesPanel.tsx           | 196 ++++++++++++---------
 1 file changed, 117 insertions(+), 79 deletions(-)

(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
index da5025b..b2c2e58 100644
--- a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
+++ b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
@@ -1,82 +1,116 @@
 import { useMemo } from "react";
-import type { ContractWithRelations, ContractPhase } from "../../lib/api";
+import type { ContractWithRelations, ContractPhase, ContractType } from "../../lib/api";
 
 // Phase deliverables configuration (mirrors backend phase_guidance.rs)
-interface RecommendedFile {
-  templateId: string;
+// IDs must match backend phase_guidance.rs exactly for mark_deliverable_complete
+interface PhaseDeliverable {
+  id: string;  // Must match backend deliverable ID
   name: string;
   priority: "required" | "recommended" | "optional";
   description: string;
 }
 
-interface PhaseDeliverables {
-  phase: ContractPhase;
-  files: RecommendedFile[];
+interface PhaseConfig {
+  deliverables: PhaseDeliverable[];
   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.",
+// Contract type specific deliverables (must match backend phase_guidance.rs)
+type ContractTypeDeliverables = Partial<Record<ContractPhase, PhaseConfig>>;
+
+const CONTRACT_TYPE_DELIVERABLES: Record<ContractType, ContractTypeDeliverables> = {
+  simple: {
+    plan: {
+      deliverables: [
+        { id: "plan-document", name: "Plan", priority: "required", description: "Implementation plan detailing the approach and tasks" },
+      ],
+      requiresRepository: true,
+      requiresTasks: false,
+      guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.",
+    },
+    execute: {
+      deliverables: [
+        { id: "pull-request", name: "Pull Request", priority: "required", description: "Pull request with the implemented changes" },
+      ],
+      requiresRepository: true,
+      requiresTasks: true,
+      guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks to finish the contract.",
+    },
   },
-  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.",
+  specification: {
+    research: {
+      deliverables: [
+        { id: "research-notes", name: "Research Notes", priority: "required", description: "Document findings and insights during research" },
+      ],
+      requiresRepository: false,
+      requiresTasks: false,
+      guidance: "Focus on understanding the problem space and document your findings in the Research Notes before moving to Specify phase.",
+    },
+    specify: {
+      deliverables: [
+        { id: "requirements-document", name: "Requirements Document", priority: "required", description: "Define functional and non-functional requirements" },
+      ],
+      requiresRepository: false,
+      requiresTasks: false,
+      guidance: "Define what needs to be built with clear requirements in the Requirements Document. Ensure specifications are detailed enough for planning.",
+    },
+    plan: {
+      deliverables: [
+        { id: "plan-document", name: "Plan", priority: "required", description: "Implementation plan detailing the approach and tasks" },
+      ],
+      requiresRepository: true,
+      requiresTasks: false,
+      guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.",
+    },
+    execute: {
+      deliverables: [
+        { id: "pull-request", name: "Pull Request", priority: "required", description: "Pull request with the implemented changes" },
+      ],
+      requiresRepository: true,
+      requiresTasks: true,
+      guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks before moving to Review phase.",
+    },
+    review: {
+      deliverables: [
+        { id: "release-notes", name: "Release Notes", priority: "required", description: "Document changes for release communication" },
+      ],
+      requiresRepository: false,
+      requiresTasks: false,
+      guidance: "Review completed work and document the release in the Release Notes. The contract can be completed after review.",
+    },
   },
   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.",
+    execute: {
+      deliverables: [], // No deliverables for execute-only contract type
+      requiresRepository: true,
+      requiresTasks: true,
+      guidance: "Execute the tasks directly. No deliverable documents are required for this contract type.",
+    },
   },
-  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" },
-    ],
+};
+
+// Get phase config for a specific contract type and phase
+function getPhaseConfig(contractType: ContractType, phase: ContractPhase): PhaseConfig {
+  const typeConfig = CONTRACT_TYPE_DELIVERABLES[contractType];
+  const phaseConfig = typeConfig?.[phase];
+
+  if (phaseConfig) {
+    return phaseConfig;
+  }
+
+  // Fallback for unknown phase/type combinations
+  return {
+    deliverables: [],
     requiresRepository: false,
     requiresTasks: false,
-    guidance: "Review work and document the release.",
-  },
-};
+    guidance: `Unknown phase "${phase}" for contract type "${contractType}"`,
+  };
+}
 
 interface DeliverableStatus {
-  templateId: string;
+  id: string;
   name: string;
   priority: "required" | "recommended" | "optional";
   description: string;
@@ -91,29 +125,33 @@ interface PhaseDeliverablesProps {
 }
 
 export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDeliverablesProps) {
-  const deliverables = PHASE_DELIVERABLES[contract.phase];
+  // Get phase config based on contract type AND phase
+  const phaseConfig = useMemo(
+    () => getPhaseConfig(contract.contractType, contract.phase),
+    [contract.contractType, contract.phase]
+  );
 
   // Calculate deliverable status
-  const fileStatuses = useMemo((): DeliverableStatus[] => {
-    return deliverables.files.map((rec) => {
+  const deliverableStatuses = useMemo((): DeliverableStatus[] => {
+    return phaseConfig.deliverables.map((deliverable) => {
       // Find matching file by name similarity
       const matchedFile = contract.files.find((f) => {
         const nameLower = f.name.toLowerCase();
-        const recLower = rec.name.toLowerCase();
+        const deliverableLower = deliverable.name.toLowerCase();
         return (
           f.contractPhase === contract.phase &&
-          (nameLower.includes(recLower) || recLower.includes(nameLower) || nameLower.includes(rec.templateId.replace("-", " ")))
+          (nameLower.includes(deliverableLower) || deliverableLower.includes(nameLower) || nameLower.includes(deliverable.id.replace("-", " ")))
         );
       });
 
       return {
-        ...rec,
+        ...deliverable,
         completed: !!matchedFile,
         fileId: matchedFile?.id,
         actualName: matchedFile?.name,
       };
     });
-  }, [contract.files, contract.phase, deliverables.files]);
+  }, [contract.files, contract.phase, phaseConfig.deliverables]);
 
   // Check repository status
   const hasRepository = contract.repositories.length > 0;
@@ -133,8 +171,8 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
     let completed = 0;
     let total = 0;
 
-    // Count required and recommended files
-    fileStatuses.forEach((s) => {
+    // Count required and recommended deliverables
+    deliverableStatuses.forEach((s) => {
       if (s.priority !== "optional") {
         total++;
         if (s.completed) completed++;
@@ -142,19 +180,19 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
     });
 
     // Count repository if required
-    if (deliverables.requiresRepository) {
+    if (phaseConfig.requiresRepository) {
       total++;
       if (hasRepository) completed++;
     }
 
-    // Count tasks if in execute phase
-    if (deliverables.requiresTasks && taskStats.total > 0) {
+    // Count tasks if required
+    if (phaseConfig.requiresTasks && taskStats.total > 0) {
       total++;
       if (taskStats.done === taskStats.total) completed++;
     }
 
     return total > 0 ? Math.round((completed / total) * 100) : 100;
-  }, [fileStatuses, hasRepository, deliverables, taskStats]);
+  }, [deliverableStatuses, hasRepository, phaseConfig, taskStats]);
 
   const priorityColors = {
     required: "text-red-400",
@@ -182,13 +220,13 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
       </div>
 
       {/* Guidance text */}
-      <p className="font-mono text-xs text-[#555] italic">{deliverables.guidance}</p>
+      <p className="font-mono text-xs text-[#555] italic">{phaseConfig.guidance}</p>
 
-      {/* File deliverables */}
+      {/* Deliverables checklist */}
       <div className="space-y-2">
-        {fileStatuses.map((status) => (
+        {deliverableStatuses.map((status) => (
           <div
-            key={status.templateId}
+            key={status.id}
             className={`flex items-center justify-between p-2 border ${
               status.completed
                 ? "border-green-400/20 bg-green-400/5"
@@ -221,7 +259,7 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
             </div>
             {!status.completed && onCreateFile && (
               <button
-                onClick={() => onCreateFile(status.templateId, status.name)}
+                onClick={() => onCreateFile(status.id, 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
@@ -232,7 +270,7 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
       </div>
 
       {/* Repository status */}
-      {deliverables.requiresRepository && (
+      {phaseConfig.requiresRepository && (
         <div
           className={`flex items-center gap-2 p-2 border ${
             hasRepository
@@ -260,8 +298,8 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
         </div>
       )}
 
-      {/* Task status (execute phase) */}
-      {deliverables.requiresTasks && (
+      {/* Task status */}
+      {phaseConfig.requiresTasks && (
         <div
           className={`flex items-center justify-between p-2 border ${
             taskStats.total > 0 && taskStats.done === taskStats.total
-- 
cgit v1.2.3

