summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/contracts
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-26 23:49:40 +0000
committersoryu <soryu@soryu.co>2026-01-26 23:49:40 +0000
commit64cc98783d067625d633eea1142d114e324f76bb (patch)
tree5a172d365e17ba1c3fc6aa32bcb2237cf05b0d0f /makima/frontend/src/components/contracts
parent63128cc45d3b677acadb30c37b79c0e13dc2cdc1 (diff)
downloadsoryu-64cc98783d067625d633eea1142d114e324f76bb.tar.gz
soryu-64cc98783d067625d633eea1142d114e324f76bb.zip
Use phase deliverables configured in contract types
Diffstat (limited to 'makima/frontend/src/components/contracts')
-rw-r--r--makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx196
1 files changed, 117 insertions, 79 deletions
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<ContractPhase, PhaseDeliverables> = {
- 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<Record<ContractPhase, PhaseConfig>>;
+
+const CONTRACT_TYPE_DELIVERABLES: Record<ContractType, ContractTypeDeliverables> = {
+ 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
</div>
{/* Guidance text */}
- <p className="font-mono text-xs text-[#555] italic">{deliverables.guidance}</p>
+ <p className="font-mono text-xs text-[#555] italic">{phaseConfig.guidance}</p>
- {/* File deliverables */}
+ {/* Deliverables checklist */}
<div className="space-y-2">
- {fileStatuses.map((status) => (
+ {deliverableStatuses.map((status) => (
<div
- key={status.templateId}
+ key={status.id}
className={`flex items-center justify-between p-2 border ${
status.completed
? "border-green-400/20 bg-green-400/5"
@@ -221,7 +259,7 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
</div>
{!status.completed && onCreateFile && (
<button
- onClick={() => onCreateFile(status.templateId, status.name)}
+ onClick={() => onCreateFile(status.id, status.name)}
className="px-2 py-1 font-mono text-[10px] text-[#75aafc] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
>
Create
@@ -232,7 +270,7 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
</div>
{/* Repository status */}
- {deliverables.requiresRepository && (
+ {phaseConfig.requiresRepository && (
<div
className={`flex items-center gap-2 p-2 border ${
hasRepository
@@ -260,8 +298,8 @@ export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDelivera
</div>
)}
- {/* Task status (execute phase) */}
- {deliverables.requiresTasks && (
+ {/* Task status */}
+ {phaseConfig.requiresTasks && (
<div
className={`flex items-center justify-between p-2 border ${
taskStats.total > 0 && taskStats.done === taskStats.total