summaryrefslogtreecommitdiff
path: root/makima/frontend/src
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 /makima/frontend/src
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>
Diffstat (limited to 'makima/frontend/src')
-rw-r--r--makima/frontend/src/lib/api.ts38
-rw-r--r--makima/frontend/src/routes/contracts.tsx52
2 files changed, 48 insertions, 42 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);
});
}