diff options
| author | soryu <soryu@soryu.co> | 2026-01-26 17:03:45 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-26 17:03:45 +0000 |
| commit | 6328477bc459eca0243b685553dbd75b925fdc8a (patch) | |
| tree | bf3eb29258244fea61daf93e128cd91540039dbc | |
| parent | 1d83595f81fbfcc034c37c2260d695f094f5776e (diff) | |
| download | soryu-6328477bc459eca0243b685553dbd75b925fdc8a.tar.gz soryu-6328477bc459eca0243b685553dbd75b925fdc8a.zip | |
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>
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 38 | ||||
| -rw-r--r-- | makima/frontend/src/routes/contracts.tsx | 52 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 | ||||
| -rw-r--r-- | makima/src/llm/templates.rs | 13 |
4 files changed, 62 insertions, 43 deletions
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[]; } /** @@ -2045,42 +2045,6 @@ export async function getTemplate(id: string): Promise<FileTemplate> { } // ============================================================================= -// 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<ListContractTypesResponse> { - 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<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); }); } diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index a794eed..538c301 100644 --- a/makima/frontend/tsconfig.tsbuildinfo +++ b/makima/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"errors":true,"version":"5.9.3"}
\ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"errors":true,"version":"5.9.3"} diff --git a/makima/src/llm/templates.rs b/makima/src/llm/templates.rs index 7a5bd38..8d3c04d 100644 --- a/makima/src/llm/templates.rs +++ b/makima/src/llm/templates.rs @@ -35,6 +35,7 @@ pub fn all_contract_types() -> Vec<ContractTypeTemplate> { vec![ simple_contract_type(), specification_contract_type(), + execute_contract_type(), ] } @@ -69,6 +70,18 @@ fn specification_contract_type() -> ContractTypeTemplate { } } +/// Execute-only contract type for immediate task execution +fn execute_contract_type() -> ContractTypeTemplate { + ContractTypeTemplate { + id: "execute".to_string(), + name: "Execute".to_string(), + description: "A minimal workflow with only an execute phase for immediate task execution without planning documents.".to_string(), + phases: vec!["execute".to_string()], + default_phase: "execute".to_string(), + is_builtin: true, + } +} + // ============================================================================= // File Templates // ============================================================================= |
