From 64cc98783d067625d633eea1142d114e324f76bb Mon Sep 17 00:00:00 2001 From: soryu Date: Mon, 26 Jan 2026 23:49:40 +0000 Subject: Use phase deliverables configured in contract types --- .../contracts/PhaseDeliverablesPanel.tsx | 196 ++++++++++++--------- 1 file changed, 117 insertions(+), 79 deletions(-) (limited to 'makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx') diff --git a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx index da5025b..b2c2e58 100644 --- a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx +++ b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx @@ -1,82 +1,116 @@ import { useMemo } from "react"; -import type { ContractWithRelations, ContractPhase } from "../../lib/api"; +import type { ContractWithRelations, ContractPhase, ContractType } from "../../lib/api"; // Phase deliverables configuration (mirrors backend phase_guidance.rs) -interface RecommendedFile { - templateId: string; +// IDs must match backend phase_guidance.rs exactly for mark_deliverable_complete +interface PhaseDeliverable { + id: string; // Must match backend deliverable ID name: string; priority: "required" | "recommended" | "optional"; description: string; } -interface PhaseDeliverables { - phase: ContractPhase; - files: RecommendedFile[]; +interface PhaseConfig { + deliverables: PhaseDeliverable[]; requiresRepository: boolean; requiresTasks: boolean; guidance: string; } -const PHASE_DELIVERABLES: Record = { - research: { - phase: "research", - files: [ - { templateId: "research-notes", name: "Research Notes", priority: "recommended", description: "Document findings and insights" }, - { templateId: "competitor-analysis", name: "Competitor Analysis", priority: "recommended", description: "Analyze competitors" }, - { templateId: "user-research", name: "User Research", priority: "optional", description: "User interviews and personas" }, - ], - requiresRepository: false, - requiresTasks: false, - guidance: "Gather information and document findings before moving to Specify.", - }, - specify: { - phase: "specify", - files: [ - { templateId: "requirements", name: "Requirements Document", priority: "required", description: "Functional and non-functional requirements" }, - { templateId: "user-stories", name: "User Stories", priority: "recommended", description: "Features from user perspective" }, - { templateId: "acceptance-criteria", name: "Acceptance Criteria", priority: "recommended", description: "Testable conditions for completion" }, - ], - requiresRepository: false, - requiresTasks: false, - guidance: "Define clear requirements and acceptance criteria.", +// Contract type specific deliverables (must match backend phase_guidance.rs) +type ContractTypeDeliverables = Partial>; + +const CONTRACT_TYPE_DELIVERABLES: Record = { + simple: { + plan: { + deliverables: [ + { id: "plan-document", name: "Plan", priority: "required", description: "Implementation plan detailing the approach and tasks" }, + ], + requiresRepository: true, + requiresTasks: false, + guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.", + }, + execute: { + deliverables: [ + { id: "pull-request", name: "Pull Request", priority: "required", description: "Pull request with the implemented changes" }, + ], + requiresRepository: true, + requiresTasks: true, + guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks to finish the contract.", + }, }, - plan: { - phase: "plan", - files: [ - { templateId: "architecture", name: "Architecture Document", priority: "recommended", description: "System architecture and design" }, - { templateId: "task-breakdown", name: "Task Breakdown", priority: "required", description: "Work broken into tasks" }, - { templateId: "technical-design", name: "Technical Design", priority: "optional", description: "Detailed technical specs" }, - ], - requiresRepository: true, - requiresTasks: false, - guidance: "Design the solution and create a task breakdown. Configure a repository.", + specification: { + research: { + deliverables: [ + { id: "research-notes", name: "Research Notes", priority: "required", description: "Document findings and insights during research" }, + ], + requiresRepository: false, + requiresTasks: false, + guidance: "Focus on understanding the problem space and document your findings in the Research Notes before moving to Specify phase.", + }, + specify: { + deliverables: [ + { id: "requirements-document", name: "Requirements Document", priority: "required", description: "Define functional and non-functional requirements" }, + ], + requiresRepository: false, + requiresTasks: false, + guidance: "Define what needs to be built with clear requirements in the Requirements Document. Ensure specifications are detailed enough for planning.", + }, + plan: { + deliverables: [ + { id: "plan-document", name: "Plan", priority: "required", description: "Implementation plan detailing the approach and tasks" }, + ], + requiresRepository: true, + requiresTasks: false, + guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.", + }, + execute: { + deliverables: [ + { id: "pull-request", name: "Pull Request", priority: "required", description: "Pull request with the implemented changes" }, + ], + requiresRepository: true, + requiresTasks: true, + guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks before moving to Review phase.", + }, + review: { + deliverables: [ + { id: "release-notes", name: "Release Notes", priority: "required", description: "Document changes for release communication" }, + ], + requiresRepository: false, + requiresTasks: false, + guidance: "Review completed work and document the release in the Release Notes. The contract can be completed after review.", + }, }, execute: { - phase: "execute", - files: [ - { templateId: "dev-notes", name: "Development Notes", priority: "recommended", description: "Implementation details" }, - { templateId: "test-plan", name: "Test Plan", priority: "optional", description: "Testing strategy" }, - { templateId: "implementation-log", name: "Implementation Log", priority: "optional", description: "Progress log" }, - ], - requiresRepository: true, - requiresTasks: true, - guidance: "Execute tasks and track implementation progress.", + execute: { + deliverables: [], // No deliverables for execute-only contract type + requiresRepository: true, + requiresTasks: true, + guidance: "Execute the tasks directly. No deliverable documents are required for this contract type.", + }, }, - review: { - phase: "review", - files: [ - { templateId: "release-notes", name: "Release Notes", priority: "required", description: "Changes for release" }, - { templateId: "review-checklist", name: "Review Checklist", priority: "recommended", description: "Code and feature review" }, - { templateId: "retrospective", name: "Retrospective", priority: "optional", description: "Project learnings" }, - ], +}; + +// Get phase config for a specific contract type and phase +function getPhaseConfig(contractType: ContractType, phase: ContractPhase): PhaseConfig { + const typeConfig = CONTRACT_TYPE_DELIVERABLES[contractType]; + const phaseConfig = typeConfig?.[phase]; + + if (phaseConfig) { + return phaseConfig; + } + + // Fallback for unknown phase/type combinations + return { + deliverables: [], requiresRepository: false, requiresTasks: false, - guidance: "Review work and document the release.", - }, -}; + guidance: `Unknown phase "${phase}" for contract type "${contractType}"`, + }; +} interface DeliverableStatus { - templateId: string; + id: string; name: string; priority: "required" | "recommended" | "optional"; description: string; @@ -91,29 +125,33 @@ interface PhaseDeliverablesProps { } export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDeliverablesProps) { - const deliverables = PHASE_DELIVERABLES[contract.phase]; + // Get phase config based on contract type AND phase + const phaseConfig = useMemo( + () => getPhaseConfig(contract.contractType, contract.phase), + [contract.contractType, contract.phase] + ); // Calculate deliverable status - const fileStatuses = useMemo((): DeliverableStatus[] => { - return deliverables.files.map((rec) => { + const deliverableStatuses = useMemo((): DeliverableStatus[] => { + return phaseConfig.deliverables.map((deliverable) => { // Find matching file by name similarity const matchedFile = contract.files.find((f) => { const nameLower = f.name.toLowerCase(); - const recLower = rec.name.toLowerCase(); + const deliverableLower = deliverable.name.toLowerCase(); return ( f.contractPhase === contract.phase && - (nameLower.includes(recLower) || recLower.includes(nameLower) || nameLower.includes(rec.templateId.replace("-", " "))) + (nameLower.includes(deliverableLower) || deliverableLower.includes(nameLower) || nameLower.includes(deliverable.id.replace("-", " "))) ); }); return { - ...rec, + ...deliverable, completed: !!matchedFile, fileId: matchedFile?.id, actualName: matchedFile?.name, }; }); - }, [contract.files, contract.phase, deliverables.files]); + }, [contract.files, contract.phase, phaseConfig.deliverables]); // Check repository status const hasRepository = contract.repositories.length > 0; @@ -133,8 +171,8 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera let completed = 0; let total = 0; - // Count required and recommended files - fileStatuses.forEach((s) => { + // Count required and recommended deliverables + deliverableStatuses.forEach((s) => { if (s.priority !== "optional") { total++; if (s.completed) completed++; @@ -142,19 +180,19 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera }); // Count repository if required - if (deliverables.requiresRepository) { + if (phaseConfig.requiresRepository) { total++; if (hasRepository) completed++; } - // Count tasks if in execute phase - if (deliverables.requiresTasks && taskStats.total > 0) { + // Count tasks if required + if (phaseConfig.requiresTasks && taskStats.total > 0) { total++; if (taskStats.done === taskStats.total) completed++; } return total > 0 ? Math.round((completed / total) * 100) : 100; - }, [fileStatuses, hasRepository, deliverables, taskStats]); + }, [deliverableStatuses, hasRepository, phaseConfig, taskStats]); const priorityColors = { required: "text-red-400", @@ -182,13 +220,13 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera {/* Guidance text */} -

{deliverables.guidance}

+

{phaseConfig.guidance}

- {/* File deliverables */} + {/* Deliverables checklist */}
- {fileStatuses.map((status) => ( + {deliverableStatuses.map((status) => (
{!status.completed && onCreateFile && (
{/* Repository status */} - {deliverables.requiresRepository && ( + {phaseConfig.requiresRepository && (
)} - {/* Task status (execute phase) */} - {deliverables.requiresTasks && ( + {/* Task status */} + {phaseConfig.requiresTasks && (
0 && taskStats.done === taskStats.total -- cgit v1.2.3