From 6364363d1418728351f252b799d397b756e1f985 Mon Sep 17 00:00:00 2001 From: soryu Date: Sat, 24 Jan 2026 20:06:28 +0000 Subject: feat: Simplify contract deliverables and add Templates UI ## Backend Changes ### Phase Deliverables Simplified - **Simple contract type**: - Plan phase: Only 'Plan' deliverable (required) - Execute phase: Only 'PR' deliverable (required) - **Specification contract type**: - Research phase: Only 'Research Notes' deliverable (required) - Specify phase: Only 'Requirements Document' deliverable (required) - Plan phase: Only 'Plan' deliverable (required) - Execute phase: Only 'PR' deliverable (required) - Review phase: Only 'Release Notes' deliverable (required) ### New 'execute' Contract Type - Only has 'execute' phase (no plan or review phases) - NO deliverables at all - executes tasks directly - Added to ContractType enum with proper Display/FromStr implementations - Added helper methods: `initial_phase()`, `terminal_phase()` ### API Updates - Added `get_phase_deliverables_for_type()` for contract-type-aware deliverables - Added `get_phase_checklist_for_type()` for contract-type-aware checklists - Added `check_phase_completion_for_type()` for contract-type-aware completion checks - Added `check_deliverables_met()` function for deliverable validation - Added `should_auto_progress()` for autonomous contract progression - Added new ContractToolRequest::CheckDeliverablesMet tool ## Frontend Changes (makima/frontend) ### Templates Page - Add TemplateEditor component for editing phase deliverables - Create Templates page with template card grid layout - Add navigation link in NavStrip - Implement three built-in templates: Simple, Specification, Execute - Support for creating custom templates with configurable phases/deliverables - Templates are persisted to localStorage Co-Authored-By: Claude Opus 4.5 --- makima/frontend/src/components/NavStrip.tsx | 1 + .../src/components/templates/TemplateEditor.tsx | 248 +++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 makima/frontend/src/components/templates/TemplateEditor.tsx (limited to 'makima/frontend/src/components') diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index 7e12c75..2838469 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -15,6 +15,7 @@ const NAV_LINKS: NavLink[] = [ { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, { label: "History", href: "/history", requiresAuth: true }, + { label: "Templates", href: "/templates", requiresAuth: true }, ]; export function NavStrip() { diff --git a/makima/frontend/src/components/templates/TemplateEditor.tsx b/makima/frontend/src/components/templates/TemplateEditor.tsx new file mode 100644 index 0000000..03382f3 --- /dev/null +++ b/makima/frontend/src/components/templates/TemplateEditor.tsx @@ -0,0 +1,248 @@ +import { useState } from "react"; +import type { ContractTemplate, Phase, Deliverable } from "../../types/templates"; + +interface Props { + template: ContractTemplate; + onSave: (template: ContractTemplate) => void; + onCancel: () => void; +} + +export function TemplateEditor({ template, onSave, onCancel }: Props) { + const [editedTemplate, setEditedTemplate] = useState({ + ...template, + phases: template.phases.map((p) => ({ + ...p, + deliverables: [...p.deliverables], + })), + }); + const [newDeliverableName, setNewDeliverableName] = useState<{ + [phaseId: string]: string; + }>({}); + + const handlePhaseNameChange = (phaseId: string, newName: string) => { + setEditedTemplate((prev) => ({ + ...prev, + phases: prev.phases.map((p) => + p.id === phaseId ? { ...p, name: newName } : p + ), + })); + }; + + const handleDeliverableNameChange = ( + phaseId: string, + deliverableId: string, + newName: string + ) => { + setEditedTemplate((prev) => ({ + ...prev, + phases: prev.phases.map((p) => + p.id === phaseId + ? { + ...p, + deliverables: p.deliverables.map((d) => + d.id === deliverableId ? { ...d, name: newName } : d + ), + } + : p + ), + })); + }; + + const handleAddDeliverable = (phaseId: string) => { + const name = newDeliverableName[phaseId]?.trim(); + if (!name) return; + + const newDeliverable: Deliverable = { + id: `deliverable-${Date.now()}`, + name, + }; + + setEditedTemplate((prev) => ({ + ...prev, + phases: prev.phases.map((p) => + p.id === phaseId + ? { ...p, deliverables: [...p.deliverables, newDeliverable] } + : p + ), + })); + setNewDeliverableName((prev) => ({ ...prev, [phaseId]: "" })); + }; + + const handleRemoveDeliverable = (phaseId: string, deliverableId: string) => { + setEditedTemplate((prev) => ({ + ...prev, + phases: prev.phases.map((p) => + p.id === phaseId + ? { + ...p, + deliverables: p.deliverables.filter((d) => d.id !== deliverableId), + } + : p + ), + })); + }; + + const handleAddPhase = () => { + const newPhase: Phase = { + id: `phase-${Date.now()}`, + name: "New Phase", + deliverables: [], + }; + setEditedTemplate((prev) => ({ + ...prev, + phases: [...prev.phases, newPhase], + })); + }; + + const handleRemovePhase = (phaseId: string) => { + setEditedTemplate((prev) => ({ + ...prev, + phases: prev.phases.filter((p) => p.id !== phaseId), + })); + }; + + return ( +
+ {/* Header */} +
+

+ Edit Template: {template.name} +

+

+ {template.description} +

+
+ + {/* Phases */} +
+ {editedTemplate.phases.map((phase, phaseIndex) => ( +
+ {/* Phase Header */} +
+ + {phaseIndex + 1} + + handlePhaseNameChange(phase.id, e.target.value)} + placeholder="Phase name" + /> + {!template.isBuiltIn && ( + + )} +
+ + {/* Deliverables */} +
+ {phase.deliverables.length === 0 ? ( +
+ No deliverables +
+ ) : ( + phase.deliverables.map((deliverable) => ( +
+ - + + handleDeliverableNameChange( + phase.id, + deliverable.id, + e.target.value + ) + } + /> + +
+ )) + )} + + {/* Add Deliverable */} +
+ + setNewDeliverableName((prev) => ({ + ...prev, + [phase.id]: e.target.value, + })) + } + onKeyPress={(e) => { + if (e.key === "Enter") { + handleAddDeliverable(phase.id); + } + }} + /> + +
+
+
+ ))} +
+ + {/* Add Phase (only for custom templates) */} + {!template.isBuiltIn && ( + + )} + + {/* Footer Actions */} +
+ + +
+
+ ); +} -- cgit v1.2.3