From 6328477bc459eca0243b685553dbd75b925fdc8a Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Mon, 26 Jan 2026 17:03:45 +0000
Subject: Add dynamic contract type templates with user customization (#33)

- Add 'execute' contract type as a built-in template in backend
- Fix API response field name mismatch (types -> contractTypes)
- Remove duplicate ContractTypeTemplate definition in api.ts
- Merge built-in types from API with user templates from localStorage
- User templates created in the Templates page now appear in contract creation

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
---
 makima/frontend/src/routes/contracts.tsx | 52 +++++++++++++++++++++++++++++---
 1 file changed, 47 insertions(+), 5 deletions(-)

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

diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx
index 4f74692..36eb980 100644
--- a/makima/frontend/src/routes/contracts.tsx
+++ b/makima/frontend/src/routes/contracts.tsx
@@ -93,19 +93,49 @@ function ContractsPageContent() {
   const [contractTypes, setContractTypes] = useState<ContractTypeTemplate[]>([]);
   const [contractTypesLoading, setContractTypesLoading] = useState(false);
 
-  // Fetch contract types when modal opens
+  // Fetch contract types when modal opens - merges built-in types with user templates
   useEffect(() => {
     if (isCreating) {
       setContractTypesLoading(true);
+
+      // Load user templates from localStorage
+      const loadUserTemplates = (): ContractTypeTemplate[] => {
+        try {
+          const saved = localStorage.getItem("makima_contract_templates");
+          if (saved) {
+            const templates = JSON.parse(saved);
+            // Convert user templates to ContractTypeTemplate format, excluding built-ins
+            return templates
+              .filter((t: { isBuiltIn?: boolean }) => !t.isBuiltIn)
+              .map((t: { id: string; name: string; description: string; phases: { id: string }[] }) => ({
+                id: t.id,
+                name: t.name,
+                description: t.description,
+                phases: t.phases.map((p: { id: string }) => p.id) as ContractPhase[],
+                defaultPhase: (t.phases[0]?.id || "execute") as ContractPhase,
+                isBuiltin: false,
+              }));
+          }
+        } catch {
+          // Ignore localStorage errors
+        }
+        return [];
+      };
+
       listContractTypes()
         .then((res) => {
-          setContractTypes(res.types);
+          // Merge built-in types from API with user templates from localStorage
+          const userTemplates = loadUserTemplates();
+          // Filter out any user templates that have the same ID as built-in types
+          const builtinIds = new Set(res.contractTypes.map(t => t.id));
+          const uniqueUserTemplates = userTemplates.filter(t => !builtinIds.has(t.id));
+          setContractTypes([...res.contractTypes, ...uniqueUserTemplates]);
           setContractTypesLoading(false);
         })
         .catch((err) => {
           console.error("Failed to fetch contract types:", err);
-          // Fall back to built-in types
-          setContractTypes([
+          // Fall back to built-in types + user templates
+          const builtinTypes: ContractTypeTemplate[] = [
             {
               id: "simple",
               name: "Simple",
@@ -122,7 +152,19 @@ function ContractsPageContent() {
               defaultPhase: "research",
               isBuiltin: true,
             },
-          ]);
+            {
+              id: "execute",
+              name: "Execute",
+              description: "Execute only: Minimal workflow for immediate task execution",
+              phases: ["execute"],
+              defaultPhase: "execute",
+              isBuiltin: true,
+            },
+          ];
+          const userTemplates = loadUserTemplates();
+          const builtinIds = new Set(builtinTypes.map(t => t.id));
+          const uniqueUserTemplates = userTemplates.filter(t => !builtinIds.has(t.id));
+          setContractTypes([...builtinTypes, ...uniqueUserTemplates]);
           setContractTypesLoading(false);
         });
     }
-- 
cgit v1.2.3

