From 6328477bc459eca0243b685553dbd75b925fdc8a Mon Sep 17 00:00:00 2001 From: soryu 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 --- makima/frontend/src/lib/api.ts | 38 +---------------------- makima/frontend/src/routes/contracts.tsx | 52 +++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 42 deletions(-) (limited to 'makima/frontend/src') diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 64ce591..b3c18a5 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -1529,7 +1529,7 @@ export interface ContractTypeTemplate { /** Response from list contract types endpoint */ export interface ListContractTypesResponse { - types: ContractTypeTemplate[]; + contractTypes: ContractTypeTemplate[]; } /** @@ -2044,42 +2044,6 @@ export async function getTemplate(id: string): Promise { return res.json(); } -// ============================================================================= -// Contract Type Templates (Workflow Definitions) -// ============================================================================= - -/** A contract type template defining a workflow */ -export interface ContractTypeTemplate { - /** Unique identifier (e.g., 'simple', 'specification', 'feature-development') */ - id: string; - /** Display name */ - name: string; - /** What this contract type is for */ - description: string; - /** Ordered list of phases in the workflow */ - phases: string[]; - /** Starting phase */ - defaultPhase: string; - /** True for built-in types ('simple', 'specification') */ - isBuiltin: boolean; -} - -export interface ListContractTypesResponse { - contractTypes: ContractTypeTemplate[]; -} - -/** - * List all available contract type templates. - * Returns built-in types (simple, specification) and any custom types. - */ -export async function listContractTypes(): Promise { - const res = await authFetch(`${API_BASE}/api/v1/contract-types`); - if (!res.ok) { - throw new Error(`Failed to list contract types: ${res.statusText}`); - } - return res.json(); -} - // ============================================================================= // Supervisor Question Types and Functions // ============================================================================= 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([]); 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