diff options
| author | soryu <soryu@soryu.co> | 2026-05-01 23:56:51 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-01 23:56:51 +0100 |
| commit | e11759447b1ac00becfb1e979e488f7f9c9cf478 (patch) | |
| tree | f8a58368de3f6dda3f2f5c1af34e869a0e714205 /makima/frontend/src/components/contracts/PhaseDeliverablesPanel.tsx | |
| parent | 80085c7cfa9d679ed3e3fd54a7d55fa8ab1addef (diff) | |
| download | soryu-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.tsx | 339 |
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> - ); -} |
