summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-05-01 23:56:51 +0100
committerGitHub <noreply@github.com>2026-05-01 23:56:51 +0100
commite11759447b1ac00becfb1e979e488f7f9c9cf478 (patch)
treef8a58368de3f6dda3f2f5c1af34e869a0e714205 /makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
parent80085c7cfa9d679ed3e3fd54a7d55fa8ab1addef (diff)
downloadsoryu-e11759447b1ac00becfb1e979e488f7f9c9cf478.tar.gz
soryu-e11759447b1ac00becfb1e979e488f7f9c9cf478.zip
chore(cleanup): Phase 5 contracts removal + tmp directive + 30-day expiry + scroll fix (#118)
Sweeping cleanup across the surface and the wire. Net: -14k LOC of legacy contracts code, plus the tmp/scroll/UX fixes the user asked for. ## Sidebar/editor independent scroll Replace `height: calc(100vh - 80px)` (which assumed an 80px masthead and quietly clipped or pushed the whole page below the fold when the masthead was taller) with `h-screen + overflow-hidden` on the page root and proper `flex-1 min-h-0` sizing on `<main>`. Sidebar and editor pane now manage their own scroll independently; the page itself never scrolls. Same fix in /tmp/:taskId. ## tmp directive — real backing for orphans/ephemerals New migration `20260501100000_tmp_directive_and_clear_orphans.sql`: * Adds `directives.is_tmp` BOOLEAN NOT NULL DEFAULT false. * Partial unique index `(owner_id) WHERE is_tmp` — at most ONE tmp directive per owner. * Hard-deletes every existing orphan task (`directive_id IS NULL`). Per the user spec: "ALSO there are TOO MANY old tasks in tmp, we need to remove all of them as well." New repository helpers: * `get_or_create_tmp_directive(pool, owner_id) -> Directive` INSERT ON CONFLICT DO NOTHING + fallback SELECT, race-safe. * `list_all_tmp_directives` — drives the expiry sweep. * `delete_expired_tmp_tasks(tmp_directive_id) -> u64`. * `list_tmp_tasks_for_owner` (replaces `list_orphan_tasks_for_owner`). `mesh::create_task`: every top-level task must have a directive. If a caller doesn't supply `directive_id` and isn't a subtask, attach to the caller's tmp directive (auto-creating it on first use). `list_directives_for_owner` filters out `is_tmp=true` so the scratchpad directive doesn't pollute the contract list — surfaced via the sidebar's `tmp/` folder instead. ## 30-day expiry on tmp tasks New `phase_tmp_expiry` in the directive reconciler. Throttled to once per hour: enumerates every tmp directive, calls `delete_expired_tmp_tasks`, logs the count. The actual delete is `WHERE created_at < NOW() - INTERVAL '30 days'` and is fast on the existing index. Subtasks die via FK cascade. ## Phase 5 — contracts removed ### Frontend Deleted entire `/contracts` surface: * routes: `contracts.tsx`, `contract-file.tsx` * components/contracts: ContractList, ContractDetail, ContractCliInput, ContractContextMenu, CommandModePanel, PhaseBadge, PhaseHint, PhaseDeliverablesPanel, PhaseProgressBar, QuickActionButtons, RepositoryPanel, TaskDerivationPreview * (Kept `PhaseConfirmationModal` — used outside the contracts surface by `TaskOutput` and `PhaseConfirmationNotification`.) * Routes deregistered from `main.tsx`; nav entry removed from `NavStrip`. ### Backend handlers Deleted: `contracts.rs` (2.4k LOC), `contract_chat.rs` (3.2k LOC), `contract_daemon.rs` (~940 LOC), `contract_discuss.rs` (~590 LOC), `transcript_analysis.rs` (~690 LOC). All `/api/v1/contracts/*` routes deregistered. OpenAPI entries dropped. Module declarations removed from `server/handlers/mod.rs`. ### CLI Removed `makima contract` and `makima supervisor` subcommands. Deleted `daemon/cli/contract.rs` and `daemon/cli/supervisor.rs`. Bin dispatch trimmed (~377 LOC). ### Orchestrator Removed the contract-spawn path from `phase_execution` (`spawn_step_contract` and its caller). `directive_steps.contract_type` now logs a warning and falls through to standalone-task spawn. Column itself stays — old data still reads, just no longer triggers a contract+supervisor spawn. ### TUI `Action::PerformCreateContract` is now a no-op that surfaces a status message: "Contracts have been removed. Use directives instead." The TUI form is dead code pending a wider refresh. ## Out of scope (deliberately left) * Contracts DB tables (`contracts`, `contract_repositories`, `contract_chat_history`, `contract_events`, `contract_templates`) are retained for historical data + because some peripheral code still joins to them in TaskSummary queries. * `mesh_supervisor` handlers are retained — they aren't only used by contracts (some mesh-level supervisor behaviour persists), and the cross-cutting cleanup is bigger than this PR. * `directive_steps.contract_type` column itself isn't dropped; just no longer functional. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx')
-rw-r--r--makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx339
1 files changed, 0 insertions, 339 deletions
diff --git a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx b/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
deleted file mode 100644
index b2c2e58..0000000
--- a/makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx
+++ /dev/null
@@ -1,339 +0,0 @@
-import { useMemo } from "react";
-import type { ContractWithRelations, ContractPhase, ContractType } from "../../lib/api";
-
-// Phase deliverables configuration (mirrors backend phase_guidance.rs)
-// 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 PhaseConfig {
- deliverables: PhaseDeliverable[];
- requiresRepository: boolean;
- requiresTasks: boolean;
- guidance: string;
-}
-
-// 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.",
- },
- },
- 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: {
- 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.",
- },
- },
-};
-
-// 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: `Unknown phase "${phase}" for contract type "${contractType}"`,
- };
-}
-
-interface DeliverableStatus {
- id: string;
- name: string;
- priority: "required" | "recommended" | "optional";
- description: string;
- completed: boolean;
- fileId?: string;
- actualName?: string;
-}
-
-interface PhaseDeliverablesProps {
- contract: ContractWithRelations;
- onCreateFile?: (templateId: string, suggestedName: string) => void;
-}
-
-export function PhaseDeliverablesPanel({ contract, onCreateFile }: PhaseDeliverablesProps) {
- // 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 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 deliverableLower = deliverable.name.toLowerCase();
- return (
- f.contractPhase === contract.phase &&
- (nameLower.includes(deliverableLower) || deliverableLower.includes(nameLower) || nameLower.includes(deliverable.id.replace("-", " ")))
- );
- });
-
- return {
- ...deliverable,
- completed: !!matchedFile,
- fileId: matchedFile?.id,
- actualName: matchedFile?.name,
- };
- });
- }, [contract.files, contract.phase, phaseConfig.deliverables]);
-
- // Check repository status
- const hasRepository = contract.repositories.length > 0;
-
- // Check task status
- const taskStats = useMemo(() => {
- const total = contract.tasks.length;
- const done = contract.tasks.filter((t) => t.status === "done" || t.status === "merged").length;
- const pending = contract.tasks.filter((t) => t.status === "pending").length;
- const running = contract.tasks.filter((t) => ["running", "initializing", "starting"].includes(t.status)).length;
- const failed = contract.tasks.filter((t) => t.status === "failed").length;
- return { total, done, pending, running, failed };
- }, [contract.tasks]);
-
- // Calculate completion percentage
- const completionPercent = useMemo(() => {
- let completed = 0;
- let total = 0;
-
- // Count required and recommended deliverables
- deliverableStatuses.forEach((s) => {
- if (s.priority !== "optional") {
- total++;
- if (s.completed) completed++;
- }
- });
-
- // Count repository if required
- if (phaseConfig.requiresRepository) {
- total++;
- if (hasRepository) completed++;
- }
-
- // 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;
- }, [deliverableStatuses, hasRepository, phaseConfig, taskStats]);
-
- const priorityColors = {
- required: "text-red-400",
- recommended: "text-yellow-400",
- optional: "text-[#555]",
- };
-
- return (
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <h3 className="font-mono text-xs text-[#75aafc] uppercase">
- Phase Deliverables
- </h3>
- <div className="flex items-center gap-2">
- <div className="w-24 h-1.5 bg-[rgba(117,170,252,0.1)] rounded overflow-hidden">
- <div
- className={`h-full transition-all duration-300 ${
- completionPercent === 100 ? "bg-green-400" : "bg-[#75aafc]"
- }`}
- style={{ width: `${completionPercent}%` }}
- />
- </div>
- <span className="font-mono text-[10px] text-[#555]">{completionPercent}%</span>
- </div>
- </div>
-
- {/* Guidance text */}
- <p className="font-mono text-xs text-[#555] italic">{phaseConfig.guidance}</p>
-
- {/* Deliverables checklist */}
- <div className="space-y-2">
- {deliverableStatuses.map((status) => (
- <div
- key={status.id}
- className={`flex items-center justify-between p-2 border ${
- status.completed
- ? "border-green-400/20 bg-green-400/5"
- : "border-[rgba(117,170,252,0.15)]"
- }`}
- >
- <div className="flex items-center gap-2">
- <span
- className={`font-mono text-xs ${
- status.completed ? "text-green-400" : "text-[#555]"
- }`}
- >
- {status.completed ? "[+]" : "[ ]"}
- </span>
- <div>
- <div className="flex items-center gap-2">
- <span className="font-mono text-xs text-[#dbe7ff]">
- {status.completed ? status.actualName : status.name}
- </span>
- {!status.completed && (
- <span className={`font-mono text-[9px] uppercase ${priorityColors[status.priority]}`}>
- {status.priority}
- </span>
- )}
- </div>
- <span className="font-mono text-[10px] text-[#555]">
- {status.description}
- </span>
- </div>
- </div>
- {!status.completed && onCreateFile && (
- <button
- 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
- </button>
- )}
- </div>
- ))}
- </div>
-
- {/* Repository status */}
- {phaseConfig.requiresRepository && (
- <div
- className={`flex items-center gap-2 p-2 border ${
- hasRepository
- ? "border-green-400/20 bg-green-400/5"
- : "border-[rgba(117,170,252,0.15)]"
- }`}
- >
- <span
- className={`font-mono text-xs ${
- hasRepository ? "text-green-400" : "text-[#555]"
- }`}
- >
- {hasRepository ? "[+]" : "[ ]"}
- </span>
- <div>
- <span className="font-mono text-xs text-[#dbe7ff]">
- Repository Configured
- </span>
- {!hasRepository && (
- <span className="font-mono text-[9px] uppercase text-red-400 ml-2">
- required
- </span>
- )}
- </div>
- </div>
- )}
-
- {/* Task status */}
- {phaseConfig.requiresTasks && (
- <div
- className={`flex items-center justify-between p-2 border ${
- taskStats.total > 0 && taskStats.done === taskStats.total
- ? "border-green-400/20 bg-green-400/5"
- : "border-[rgba(117,170,252,0.15)]"
- }`}
- >
- <div className="flex items-center gap-2">
- <span
- className={`font-mono text-xs ${
- taskStats.total > 0 && taskStats.done === taskStats.total
- ? "text-green-400"
- : "text-[#555]"
- }`}
- >
- {taskStats.total > 0 && taskStats.done === taskStats.total ? "[+]" : "[ ]"}
- </span>
- <span className="font-mono text-xs text-[#dbe7ff]">
- Tasks Completed
- </span>
- </div>
- {taskStats.total > 0 ? (
- <span className="font-mono text-[10px] text-[#9bc3ff]">
- {taskStats.done}/{taskStats.total}
- {taskStats.running > 0 && ` (${taskStats.running} running)`}
- {taskStats.failed > 0 && (
- <span className="text-red-400"> ({taskStats.failed} failed)</span>
- )}
- </span>
- ) : (
- <span className="font-mono text-[10px] text-[#555]">No tasks yet</span>
- )}
- </div>
- )}
- </div>
- );
-}