summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-26 17:03:45 +0000
committerGitHub <noreply@github.com>2026-01-26 17:03:45 +0000
commit6328477bc459eca0243b685553dbd75b925fdc8a (patch)
treebf3eb29258244fea61daf93e128cd91540039dbc
parent1d83595f81fbfcc034c37c2260d695f094f5776e (diff)
downloadsoryu-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.ts38
-rw-r--r--makima/frontend/src/routes/contracts.tsx52
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/src/llm/templates.rs13
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
// =============================================================================