diff options
Diffstat (limited to 'makima/frontend')
| -rw-r--r-- | makima/frontend/src/components/NavStrip.tsx | 1 | ||||
| -rw-r--r-- | makima/frontend/src/components/contracts/ContractList.tsx | 5 | ||||
| -rw-r--r-- | makima/frontend/src/components/templates/TemplateEditor.tsx | 257 | ||||
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 110 | ||||
| -rw-r--r-- | makima/frontend/src/main.tsx | 9 | ||||
| -rw-r--r-- | makima/frontend/src/routes/contracts.tsx | 58 | ||||
| -rw-r--r-- | makima/frontend/src/routes/templates.tsx | 388 | ||||
| -rw-r--r-- | makima/frontend/src/types/templates.ts | 90 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 |
9 files changed, 3 insertions, 917 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index f44799b..fb95c7f 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -14,7 +14,6 @@ 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/contracts/ContractList.tsx b/makima/frontend/src/components/contracts/ContractList.tsx index 532ab87..98f8ff6 100644 --- a/makima/frontend/src/components/contracts/ContractList.tsx +++ b/makima/frontend/src/components/contracts/ContractList.tsx @@ -136,11 +136,6 @@ export function ContractList({ Local </span> )} - {contract.redTeamEnabled && ( - <span className="px-1.5 py-0.5 font-mono text-[9px] uppercase text-cyan-400 border border-cyan-400/30 bg-cyan-400/10 shrink-0" title="Red Team monitoring enabled"> - 🔍 Red Team - </span> - )} </div> <span className={`text-[10px] font-mono uppercase shrink-0 ${ diff --git a/makima/frontend/src/components/templates/TemplateEditor.tsx b/makima/frontend/src/components/templates/TemplateEditor.tsx deleted file mode 100644 index c8e1f98..0000000 --- a/makima/frontend/src/components/templates/TemplateEditor.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { useState } from "react"; -import type { ContractTemplate, Phase, Deliverable } from "../../types/templates"; - -interface Props { - template: ContractTemplate; - onSave: (template: ContractTemplate) => void; - onCancel: () => void; - readOnly?: boolean; -} - -export function TemplateEditor({ template, onSave, onCancel, readOnly = false }: Props) { - const [editedTemplate, setEditedTemplate] = useState<ContractTemplate>({ - ...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 ( - <div className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.35)] p-6"> - {/* Header */} - <div className="mb-6 pb-4 border-b border-[rgba(117,170,252,0.15)]"> - <h2 className="text-sm font-mono uppercase tracking-wide text-[#9bc3ff] mb-1"> - {readOnly ? "View" : "Edit"} Template: {template.name} - </h2> - <p className="text-xs font-mono text-[#75aafc] opacity-70"> - {template.description} - </p> - {readOnly && ( - <p className="text-xs font-mono text-amber-400 mt-2"> - Built-in templates are read-only - </p> - )} - </div> - - {/* Phases */} - <div className="space-y-4 mb-6"> - {editedTemplate.phases.map((phase, phaseIndex) => ( - <div - key={phase.id} - className="bg-[rgba(0,0,0,0.3)] border border-[rgba(117,170,252,0.2)] p-4" - > - {/* Phase Header */} - <div className="flex items-center gap-3 mb-3"> - <span className="w-6 h-6 flex items-center justify-center bg-[rgba(117,170,252,0.2)] text-[#9bc3ff] text-xs font-mono"> - {phaseIndex + 1} - </span> - <input - type="text" - className="flex-1 px-3 py-1.5 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3] disabled:opacity-60" - value={phase.name} - onChange={(e) => handlePhaseNameChange(phase.id, e.target.value)} - placeholder="Phase name" - disabled={readOnly} - /> - {!template.isBuiltIn && ( - <button - type="button" - onClick={() => handleRemovePhase(phase.id)} - className="w-7 h-7 flex items-center justify-center border border-[rgba(255,100,100,0.3)] text-[#ff6464] hover:bg-[rgba(255,100,100,0.1)] transition-colors text-sm" - title="Remove phase" - > - x - </button> - )} - </div> - - {/* Deliverables */} - <div className="ml-9 space-y-2"> - {phase.deliverables.length === 0 ? ( - <div className="text-xs font-mono text-[#556677] italic"> - No deliverables - </div> - ) : ( - phase.deliverables.map((deliverable) => ( - <div - key={deliverable.id} - className="flex items-center gap-2" - > - <span className="text-[#75aafc] text-xs">-</span> - <input - type="text" - className="flex-1 px-2 py-1 bg-transparent border border-[rgba(117,170,252,0.15)] text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#3f6fb3]" - value={deliverable.name} - onChange={(e) => - handleDeliverableNameChange( - phase.id, - deliverable.id, - e.target.value - ) - } - /> - <button - type="button" - onClick={() => - handleRemoveDeliverable(phase.id, deliverable.id) - } - className="w-5 h-5 flex items-center justify-center text-[#ff6464] hover:bg-[rgba(255,100,100,0.1)] transition-colors text-xs" - title="Remove deliverable" - > - x - </button> - </div> - )) - )} - - {/* Add Deliverable */} - <div className="flex items-center gap-2 pt-2"> - <input - type="text" - className="flex-1 px-2 py-1 bg-transparent border border-[rgba(117,170,252,0.15)] text-[#dbe7ff] font-mono text-xs placeholder-[#445566] focus:outline-none focus:border-[#3f6fb3]" - placeholder="New deliverable name..." - value={newDeliverableName[phase.id] || ""} - onChange={(e) => - setNewDeliverableName((prev) => ({ - ...prev, - [phase.id]: e.target.value, - })) - } - onKeyPress={(e) => { - if (e.key === "Enter") { - handleAddDeliverable(phase.id); - } - }} - /> - <button - type="button" - onClick={() => handleAddDeliverable(phase.id)} - className="px-2 py-1 border border-[rgba(117,170,252,0.3)] text-[#9bc3ff] font-mono text-xs hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.1)] transition-colors" - > - + Add - </button> - </div> - </div> - </div> - ))} - </div> - - {/* Add Phase (only for custom templates) */} - {!template.isBuiltIn && ( - <button - type="button" - onClick={handleAddPhase} - className="w-full mb-6 px-4 py-2 border border-dashed border-[rgba(117,170,252,0.3)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors" - > - + Add Phase - </button> - )} - - {/* Footer Actions */} - <div className="flex gap-3 justify-end pt-4 border-t border-[rgba(117,170,252,0.15)]"> - <button - type="button" - onClick={onCancel} - className="px-4 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors" - > - {readOnly ? "Close" : "Cancel"} - </button> - {!readOnly && ( - <button - type="button" - onClick={() => onSave(editedTemplate)} - className="px-4 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors" - > - Save Changes - </button> - )} - </div> - </div> - ); -} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index e8b3d8a..f148d76 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -1659,45 +1659,9 @@ export interface DeliverableDefinition { priority: "required" | "recommended" | "optional"; } -/** Request to create a custom contract type template */ -export interface CreateTemplateRequest { - name: string; - description?: string; - phases: PhaseDefinition[]; - defaultPhase: string; - deliverables?: Record<string, DeliverableDefinition[]>; -} - -/** Request to update a custom contract type template */ -export interface UpdateTemplateRequest { - name?: string; - description?: string; - phases?: PhaseDefinition[]; - defaultPhase?: string; - deliverables?: Record<string, DeliverableDefinition[]>; - version?: number; -} - -/** Custom template record from the API */ -export interface ContractTypeTemplateRecord { - id: string; - name: string; - description: string | null; - phases: PhaseDefinition[]; - defaultPhase: string; - isBuiltin: boolean; - version: number; - createdAt: string; -} - -/** Response for single template operations */ -export interface TemplateResponse { - template: ContractTypeTemplateRecord; -} - /** - * List available contract types/templates. - * Returns built-in types (simple, specification) and any custom types. + * List available contract types. + * Returns built-in types only (simple, specification, execute). */ export async function listContractTypes(): Promise<ListContractTypesResponse> { const res = await authFetch(`${API_BASE}/api/v1/contract-types`); @@ -1707,66 +1671,6 @@ export async function listContractTypes(): Promise<ListContractTypesResponse> { return res.json(); } -/** - * Create a new custom contract type template. - */ -export async function createContractTemplate( - req: CreateTemplateRequest -): Promise<TemplateResponse> { - const res = await authFetch(`${API_BASE}/api/v1/contract-types`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - if (!res.ok) { - const err = await res.json().catch(() => ({ message: res.statusText })); - throw new Error(err.message || `Failed to create template: ${res.statusText}`); - } - return res.json(); -} - -/** - * Get a custom contract type template by ID. - */ -export async function getContractTemplate(id: string): Promise<TemplateResponse> { - const res = await authFetch(`${API_BASE}/api/v1/contract-types/${id}`); - if (!res.ok) { - throw new Error(`Failed to get template: ${res.statusText}`); - } - return res.json(); -} - -/** - * Update a custom contract type template. - */ -export async function updateContractTemplate( - id: string, - req: UpdateTemplateRequest -): Promise<TemplateResponse> { - const res = await authFetch(`${API_BASE}/api/v1/contract-types/${id}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - if (!res.ok) { - const err = await res.json().catch(() => ({ message: res.statusText })); - throw new Error(err.message || `Failed to update template: ${res.statusText}`); - } - return res.json(); -} - -/** - * Delete a custom contract type template. - */ -export async function deleteContractTemplate(id: string): Promise<void> { - const res = await authFetch(`${API_BASE}/api/v1/contract-types/${id}`, { - method: "DELETE", - }); - if (!res.ok) { - throw new Error(`Failed to delete template: ${res.statusText}`); - } -} - export interface ContractRepository { id: string; contractId: string; @@ -1792,8 +1696,6 @@ export interface ContractSummary { supervisorTaskId: string | null; /** When true, tasks won't auto-push or create PRs - use patch files instead */ localOnly: boolean; - /** When true, a red team task monitors work output for quality */ - redTeamEnabled: boolean; fileCount: number; taskCount: number; repositoryCount: number; @@ -1818,10 +1720,6 @@ export interface Contract { phaseGuard: boolean; /** When true, tasks won't auto-push or create PRs - use patch files instead */ localOnly: boolean; - /** When true, a red team task monitors work output for quality */ - redTeamEnabled: boolean; - /** Custom criteria for the red team to evaluate */ - redTeamPrompt: string | null; version: number; createdAt: string; updatedAt: string; @@ -1859,10 +1757,6 @@ export interface CreateContractRequest { initialPhase?: ContractPhase | string; /** When true, tasks won't auto-push or create PRs - use patch files instead */ localOnly?: boolean; - /** When true, spawn a red team task to monitor work output */ - redTeamEnabled?: boolean; - /** Custom criteria for the red team to evaluate */ - redTeamPrompt?: string; } export interface UpdateContractRequest { diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx index ef1ba5c..50fffe4 100644 --- a/makima/frontend/src/main.tsx +++ b/makima/frontend/src/main.tsx @@ -18,7 +18,6 @@ import HistoryPage from "./routes/history"; import LoginPage from "./routes/login"; import SettingsPage from "./routes/settings"; import ContractFilePage from "./routes/contract-file"; -import TemplatesPage from "./routes/templates"; import SpeakPage from "./routes/speak"; createRoot(document.getElementById("root")!).render( @@ -129,14 +128,6 @@ createRoot(document.getElementById("root")!).render( } /> <Route - path="/templates" - element={ - <ProtectedRoute> - <TemplatesPage /> - </ProtectedRoute> - } - /> - <Route path="/speak" element={ <ProtectedRoute> diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx index 8dcfe34..dde78b1 100644 --- a/makima/frontend/src/routes/contracts.tsx +++ b/makima/frontend/src/routes/contracts.tsx @@ -93,8 +93,6 @@ function ContractsPageContent() { const [contractTypes, setContractTypes] = useState<ContractTypeTemplate[]>([]); const [contractTypesLoading, setContractTypesLoading] = useState(false); const [localOnly, setLocalOnly] = useState(false); - const [redTeamEnabled, setRedTeamEnabled] = useState(false); - const [redTeamPrompt, setRedTeamPrompt] = useState(""); // Fetch contract types when modal opens - API returns both built-in and custom templates useEffect(() => { @@ -238,8 +236,6 @@ function ContractsPageContent() { templateId: isCustomTemplate ? contractType : undefined, initialPhase: initialPhase !== defaultPhaseForType ? initialPhase : undefined, localOnly: localOnly || undefined, - redTeamEnabled: redTeamEnabled || undefined, - redTeamPrompt: redTeamEnabled && redTeamPrompt.trim() ? redTeamPrompt.trim() : undefined, }; try { @@ -315,8 +311,6 @@ function ContractsPageContent() { setRepoUrl(""); setRepoPath(""); setLocalOnly(false); - setRedTeamEnabled(false); - setRedTeamPrompt(""); setCreateError(null); }, []); @@ -689,58 +683,6 @@ function ContractsPageContent() { </div> {/* Red Team Monitoring */} - <div className="border-t border-[rgba(117,170,252,0.2)] pt-4"> - <div className="flex items-center gap-3"> - <button - type="button" - onClick={() => setRedTeamEnabled(!redTeamEnabled)} - className={`w-5 h-5 flex items-center justify-center border transition-colors ${ - redTeamEnabled - ? "bg-[#0f3c78] border-[#75aafc] text-[#dbe7ff]" - : "bg-[#0d1b2d] border-[#3f6fb3] text-transparent" - }`} - > - {redTeamEnabled && ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="12" - height="12" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="3" - strokeLinecap="round" - strokeLinejoin="round" - > - <polyline points="20 6 9 17 4 12" /> - </svg> - )} - </button> - <label - className="font-mono text-sm text-[#dbe7ff] cursor-pointer select-none" - onClick={() => setRedTeamEnabled(!redTeamEnabled)} - > - Enable Red Team Monitoring - </label> - </div> - <p className="font-mono text-xs text-[#8b949e] pl-8"> - Spawns a parallel task to monitor work output for quality and compliance. - </p> - {redTeamEnabled && ( - <div className="mt-3 pl-8"> - <label className="block font-mono text-xs text-[#75aafc] uppercase mb-2"> - Custom Review Criteria (Optional) - </label> - <textarea - value={redTeamPrompt} - onChange={(e) => setRedTeamPrompt(e.target.value)} - placeholder="e.g., 'Focus on security best practices' or 'Ensure all functions have tests'" - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] text-sm font-mono h-20 resize-none focus:border-[#75aafc] focus:outline-none" - /> - </div> - )} - </div> - {/* Repository Configuration */} <div className="border-t border-[rgba(117,170,252,0.2)] pt-4"> <label className="block font-mono text-xs text-[#75aafc] uppercase mb-3"> diff --git a/makima/frontend/src/routes/templates.tsx b/makima/frontend/src/routes/templates.tsx deleted file mode 100644 index b2c9974..0000000 --- a/makima/frontend/src/routes/templates.tsx +++ /dev/null @@ -1,388 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import { useNavigate } from "react-router"; -import { Masthead } from "../components/Masthead"; -import { TemplateEditor } from "../components/templates/TemplateEditor"; -import { useAuth } from "../contexts/AuthContext"; -import type { ContractTemplate } from "../types/templates"; -import { DEFAULT_TEMPLATES } from "../types/templates"; -import { - listContractTypes, - createContractTemplate, - updateContractTemplate, - deleteContractTemplate, - type PhaseDefinition, - type DeliverableDefinition, -} from "../lib/api"; - -export default function TemplatesPage() { - const navigate = useNavigate(); - const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); - - const [templates, setTemplates] = useState<ContractTemplate[]>(DEFAULT_TEMPLATES); - const [loading, setLoading] = useState(true); - const [error, setError] = useState<string | null>(null); - const [saving, setSaving] = useState(false); - - const [editingTemplate, setEditingTemplate] = useState<ContractTemplate | null>( - null - ); - const [showNewTemplateForm, setShowNewTemplateForm] = useState(false); - const [newTemplateName, setNewTemplateName] = useState(""); - const [newTemplateDescription, setNewTemplateDescription] = useState(""); - - // Redirect to login if not authenticated - useEffect(() => { - if (!authLoading && isAuthConfigured && !isAuthenticated) { - navigate("/login"); - } - }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); - - // Fetch templates from API - const fetchTemplates = useCallback(async () => { - try { - setLoading(true); - setError(null); - const response = await listContractTypes(); - - // Convert API response to ContractTemplate format - const apiTemplates: ContractTemplate[] = response.contractTypes.map((t) => ({ - id: t.id, - name: t.name, - description: t.description, - isBuiltIn: t.isBuiltin, - phases: t.phases.map((phaseId) => ({ - id: phaseId, - name: t.phaseNames?.[phaseId] || phaseId.charAt(0).toUpperCase() + phaseId.slice(1), - deliverables: [], // Deliverables are managed server-side - })), - })); - - // Merge with DEFAULT_TEMPLATES to ensure we have full phase/deliverable info for built-ins - const mergedTemplates = apiTemplates.map((apiTemplate) => { - const defaultTemplate = DEFAULT_TEMPLATES.find((d) => d.id === apiTemplate.id); - if (defaultTemplate && apiTemplate.isBuiltIn) { - return defaultTemplate; // Use the richer default template for built-ins - } - return apiTemplate; - }); - - setTemplates(mergedTemplates); - } catch (err) { - console.error("Failed to fetch templates:", err); - setError(err instanceof Error ? err.message : "Failed to fetch templates"); - // Fall back to default templates - setTemplates(DEFAULT_TEMPLATES); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - if (!authLoading && isAuthenticated) { - fetchTemplates(); - } else if (!authLoading && !isAuthConfigured) { - // No auth configured, just show defaults - setTemplates(DEFAULT_TEMPLATES); - setLoading(false); - } - }, [authLoading, isAuthenticated, isAuthConfigured, fetchTemplates]); - - const handleSaveTemplate = async (updatedTemplate: ContractTemplate) => { - if (updatedTemplate.isBuiltIn) { - // Built-in templates are read-only, just close the editor - setEditingTemplate(null); - return; - } - - try { - setSaving(true); - setError(null); - - // Convert to API format - const phases: PhaseDefinition[] = updatedTemplate.phases.map((p, index) => ({ - id: p.id, - name: p.name, - order: index, - })); - - const deliverables: Record<string, DeliverableDefinition[]> = {}; - for (const phase of updatedTemplate.phases) { - if (phase.deliverables.length > 0) { - deliverables[phase.id] = phase.deliverables.map((d) => ({ - id: d.id, - name: d.name, - priority: "required" as const, - })); - } - } - - await updateContractTemplate(updatedTemplate.id, { - name: updatedTemplate.name, - description: updatedTemplate.description, - phases, - defaultPhase: phases[0]?.id || "execute", - deliverables: Object.keys(deliverables).length > 0 ? deliverables : undefined, - }); - - // Refresh templates from server - await fetchTemplates(); - setEditingTemplate(null); - } catch (err) { - console.error("Failed to update template:", err); - setError(err instanceof Error ? err.message : "Failed to update template"); - } finally { - setSaving(false); - } - }; - - const handleCreateTemplate = async () => { - if (!newTemplateName.trim()) return; - - try { - setSaving(true); - setError(null); - - const phases: PhaseDefinition[] = [ - { id: "execute", name: "Execute", order: 0 }, - ]; - - await createContractTemplate({ - name: newTemplateName.trim(), - description: newTemplateDescription.trim() || "Custom contract template", - phases, - defaultPhase: "execute", - }); - - // Refresh templates from server - await fetchTemplates(); - - setNewTemplateName(""); - setNewTemplateDescription(""); - setShowNewTemplateForm(false); - } catch (err) { - console.error("Failed to create template:", err); - setError(err instanceof Error ? err.message : "Failed to create template"); - } finally { - setSaving(false); - } - }; - - const handleDeleteTemplate = async (templateId: string) => { - const template = templates.find((t) => t.id === templateId); - if (template?.isBuiltIn) return; - - if (window.confirm(`Are you sure you want to delete "${template?.name}"?`)) { - try { - setSaving(true); - setError(null); - await deleteContractTemplate(templateId); - await fetchTemplates(); - } catch (err) { - console.error("Failed to delete template:", err); - setError(err instanceof Error ? err.message : "Failed to delete template"); - } finally { - setSaving(false); - } - } - }; - - const handleRefresh = () => { - fetchTemplates(); - }; - - // Show loading state - if (authLoading || loading) { - return ( - <div className="relative z-10 min-h-screen flex items-center justify-center bg-[#0a1628]"> - <div className="text-[#75aafc] font-mono text-sm animate-pulse"> - Loading... - </div> - </div> - ); - } - - // Editor view - if (editingTemplate) { - return ( - <div className="relative z-10 min-h-screen bg-[#0a1628]"> - <Masthead /> - <main className="max-w-4xl mx-auto px-4 py-6"> - <TemplateEditor - template={editingTemplate} - onSave={handleSaveTemplate} - onCancel={() => setEditingTemplate(null)} - readOnly={editingTemplate.isBuiltIn} - /> - </main> - </div> - ); - } - - return ( - <div className="relative z-10 min-h-screen bg-[#0a1628]"> - <Masthead /> - <main className="max-w-6xl mx-auto px-4 py-6"> - {/* Error display */} - {error && ( - <div className="mb-4 p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-xs"> - {error} - <button - type="button" - onClick={() => setError(null)} - className="ml-2 text-red-400/70 hover:text-red-400" - > - Dismiss - </button> - </div> - )} - - {/* Header */} - <div className="flex justify-between items-start mb-6 pb-4 border-b border-[rgba(117,170,252,0.15)]"> - <div> - <h1 className="text-lg font-mono uppercase tracking-wide text-[#9bc3ff] mb-1"> - Contract Templates - </h1> - <p className="text-xs font-mono text-[#75aafc] opacity-70"> - Manage contract types and their phase deliverables - </p> - </div> - <div className="flex gap-3"> - <button - type="button" - onClick={handleRefresh} - disabled={saving} - className="px-3 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors disabled:opacity-50" - > - Refresh - </button> - <button - type="button" - onClick={() => setShowNewTemplateForm(true)} - disabled={saving} - className="px-3 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors disabled:opacity-50" - > - + New Template - </button> - </div> - </div> - - {/* New Template Form */} - {showNewTemplateForm && ( - <div className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.35)] p-4 mb-6"> - <div className="flex gap-3 items-center"> - <input - type="text" - className="flex-1 px-3 py-2 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3]" - placeholder="Template name..." - value={newTemplateName} - onChange={(e) => setNewTemplateName(e.target.value)} - disabled={saving} - /> - <input - type="text" - className="flex-1 px-3 py-2 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3]" - placeholder="Description (optional)..." - value={newTemplateDescription} - onChange={(e) => setNewTemplateDescription(e.target.value)} - disabled={saving} - /> - <button - type="button" - onClick={handleCreateTemplate} - disabled={saving || !newTemplateName.trim()} - className="px-4 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors disabled:opacity-50" - > - {saving ? "Creating..." : "Create"} - </button> - <button - type="button" - onClick={() => setShowNewTemplateForm(false)} - disabled={saving} - className="px-4 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors disabled:opacity-50" - > - Cancel - </button> - </div> - </div> - )} - - {/* Templates Grid */} - <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> - {templates.map((template) => ( - <div - key={template.id} - className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.25)] hover:border-[rgba(117,170,252,0.45)] transition-colors" - > - {/* Card Header */} - <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.15)] flex items-center justify-between"> - <h3 className="font-mono text-sm text-white">{template.name}</h3> - {template.isBuiltIn ? ( - <span className="px-2 py-0.5 bg-[rgba(117,170,252,0.15)] text-[#75aafc] font-mono text-[10px] uppercase tracking-wide"> - Built-in - </span> - ) : ( - <span className="px-2 py-0.5 bg-[rgba(100,200,100,0.15)] text-[#7bc97b] font-mono text-[10px] uppercase tracking-wide"> - Custom - </span> - )} - </div> - - {/* Card Body */} - <div className="px-4 py-3"> - <p className="text-xs font-mono text-[#75aafc] opacity-70 mb-4 min-h-[2.5rem]"> - {template.description} - </p> - - {/* Phases */} - <div className="space-y-2 mb-4"> - {template.phases.map((phase, index) => ( - <div key={phase.id} className="flex items-start gap-2"> - <div className="flex flex-col items-center"> - <span className="w-2 h-2 rounded-full bg-[#3f6fb3]" /> - {index < template.phases.length - 1 && ( - <span className="w-px h-4 bg-[rgba(117,170,252,0.25)]" /> - )} - </div> - <div className="flex-1 min-w-0"> - <span className="text-xs font-mono text-[#9bc3ff]"> - {phase.name} - </span> - <span className="text-[10px] font-mono text-[#556677] ml-2"> - {phase.deliverables.length === 0 - ? "(no deliverables)" - : phase.deliverables.map((d) => d.name).join(", ")} - </span> - </div> - </div> - ))} - </div> - </div> - - {/* Card Footer */} - <div className="px-4 py-3 border-t border-[rgba(117,170,252,0.15)] flex gap-2"> - <button - type="button" - onClick={() => setEditingTemplate(template)} - disabled={saving} - className="flex-1 px-3 py-1.5 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors disabled:opacity-50" - > - {template.isBuiltIn ? "View" : "Edit"} - </button> - {!template.isBuiltIn && ( - <button - type="button" - onClick={() => handleDeleteTemplate(template.id)} - disabled={saving} - className="px-3 py-1.5 border border-[rgba(255,100,100,0.25)] text-[#ff6464] font-mono text-xs uppercase tracking-wide hover:border-[rgba(255,100,100,0.5)] hover:bg-[rgba(255,100,100,0.05)] transition-colors disabled:opacity-50" - > - Delete - </button> - )} - </div> - </div> - ))} - </div> - </main> - </div> - ); -} diff --git a/makima/frontend/src/types/templates.ts b/makima/frontend/src/types/templates.ts deleted file mode 100644 index ca337c5..0000000 --- a/makima/frontend/src/types/templates.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Contract Template types -export interface Deliverable { - id: string; - name: string; -} - -export interface Phase { - id: string; - name: string; - deliverables: Deliverable[]; -} - -export interface ContractTemplate { - id: string; - name: string; - description: string; - phases: Phase[]; - isBuiltIn: boolean; -} - -// Default built-in templates -// NOTE: Deliverable IDs must match backend phase_guidance.rs exactly -export const DEFAULT_TEMPLATES: ContractTemplate[] = [ - { - id: "simple", - name: "Simple", - description: "A simple contract with plan and execute phases.", - isBuiltIn: true, - phases: [ - { - id: "plan", - name: "Plan", - deliverables: [{ id: "plan-document", name: "Plan" }], - }, - { - id: "execute", - name: "Execute", - deliverables: [{ id: "pull-request", name: "Pull Request" }], - }, - ], - }, - { - id: "specification", - name: "Specification", - description: - "A comprehensive contract with research, specification, planning, execution, and review phases.", - isBuiltIn: true, - phases: [ - { - id: "research", - name: "Research", - deliverables: [{ id: "research-notes", name: "Research Notes" }], - }, - { - id: "specify", - name: "Specify", - deliverables: [{ id: "requirements-document", name: "Requirements Document" }], - }, - { - id: "plan", - name: "Plan", - deliverables: [{ id: "plan-document", name: "Plan" }], - }, - { - id: "execute", - name: "Execute", - deliverables: [{ id: "pull-request", name: "Pull Request" }], - }, - { - id: "review", - name: "Review", - deliverables: [{ id: "release-notes", name: "Release Notes" }], - }, - ], - }, - { - id: "execute", - name: "Execute", - description: - "A minimal contract with only an execute phase and no deliverables.", - isBuiltIn: true, - phases: [ - { - id: "execute", - name: "Execute", - deliverables: [], - }, - ], - }, -]; diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 3aa9cbf..afddaf9 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/commandmodepanel.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/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.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/mesh/worktreefilespanel.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/usespeakwebsocket.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/speak.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"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/commandmodepanel.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/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.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/mesh/worktreefilespanel.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/usespeakwebsocket.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/speak.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"}
\ No newline at end of file |
