From 52d121269195f0e799d0ab4241e4facc3c7c0596 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Mon, 19 Jan 2026 17:55:22 +0000
Subject: Add right-click context menu for contracts on contracts and board
 pages (#8)

Implement a reusable ContractContextMenu component that provides:
- Mark as Complete/Active/Archive status actions (conditionally shown)
- Go to Supervisor Task link (when supervisor exists)
- Delete action with confirmation

Integrate context menu into:
- ContractList.tsx on the contracts page
- WorkflowBoard on the workflow/board page via PhaseColumn and WorkflowContractCard

Features match ElementContextMenu patterns:
- Fixed positioning with z-50
- Click outside and Escape key close handlers
- Viewport overflow prevention
- Dark theme colors (#0a1628, #0d1b2d, #75aafc, #9bc3ff)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
---
 .../src/components/workflow/WorkflowBoard.tsx      | 66 ++++++++++++++++++----
 1 file changed, 55 insertions(+), 11 deletions(-)

(limited to 'makima/frontend/src/components/workflow/WorkflowBoard.tsx')

diff --git a/makima/frontend/src/components/workflow/WorkflowBoard.tsx b/makima/frontend/src/components/workflow/WorkflowBoard.tsx
index af4aec7..e36ca21 100644
--- a/makima/frontend/src/components/workflow/WorkflowBoard.tsx
+++ b/makima/frontend/src/components/workflow/WorkflowBoard.tsx
@@ -1,11 +1,17 @@
-import { useMemo } from "react";
+import { useMemo, useState } from "react";
 import type { ContractSummary, ContractPhase } from "../../lib/api";
 import { PhaseColumn } from "./PhaseColumn";
+import { ContractContextMenu } from "../contracts/ContractContextMenu";
 
 interface WorkflowBoardProps {
   contracts: ContractSummary[];
   onContractClick: (contractId: string) => void;
   onPhaseChange: (contractId: string, newPhase: ContractPhase) => void;
+  onMarkComplete?: (contract: ContractSummary) => void;
+  onMarkActive?: (contract: ContractSummary) => void;
+  onArchive?: (contract: ContractSummary) => void;
+  onDelete?: (contract: ContractSummary) => void;
+  onGoToSupervisor?: (contract: ContractSummary) => void;
 }
 
 const phases: ContractPhase[] = ["research", "specify", "plan", "execute", "review"];
@@ -14,7 +20,27 @@ export function WorkflowBoard({
   contracts,
   onContractClick,
   onPhaseChange,
+  onMarkComplete,
+  onMarkActive,
+  onArchive,
+  onDelete,
+  onGoToSupervisor,
 }: WorkflowBoardProps) {
+  const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null);
+  const [contextMenuContract, setContextMenuContract] = useState<ContractSummary | null>(null);
+
+  const handleContextMenu = (e: React.MouseEvent, contract: ContractSummary) => {
+    e.preventDefault();
+    e.stopPropagation(); // Prevent interference with drag-and-drop
+    setContextMenuPosition({ x: e.clientX, y: e.clientY });
+    setContextMenuContract(contract);
+  };
+
+  const closeContextMenu = () => {
+    setContextMenuPosition(null);
+    setContextMenuContract(null);
+  };
+
   // Group contracts by phase
   const contractsByPhase = useMemo(() => {
     const grouped: Record<ContractPhase, ContractSummary[]> = {
@@ -39,16 +65,34 @@ export function WorkflowBoard({
   }, [contracts]);
 
   return (
-    <div className="flex gap-2 h-full overflow-x-auto">
-      {phases.map((phase) => (
-        <PhaseColumn
-          key={phase}
-          phase={phase}
-          contracts={contractsByPhase[phase]}
-          onContractClick={onContractClick}
-          onDrop={onPhaseChange}
+    <>
+      <div className="flex gap-2 h-full overflow-x-auto">
+        {phases.map((phase) => (
+          <PhaseColumn
+            key={phase}
+            phase={phase}
+            contracts={contractsByPhase[phase]}
+            onContractClick={onContractClick}
+            onDrop={onPhaseChange}
+            onContextMenu={handleContextMenu}
+          />
+        ))}
+      </div>
+
+      {/* Context Menu */}
+      {contextMenuPosition && contextMenuContract && (
+        <ContractContextMenu
+          x={contextMenuPosition.x}
+          y={contextMenuPosition.y}
+          contract={contextMenuContract}
+          onClose={closeContextMenu}
+          onMarkComplete={() => onMarkComplete?.(contextMenuContract)}
+          onMarkActive={() => onMarkActive?.(contextMenuContract)}
+          onArchive={() => onArchive?.(contextMenuContract)}
+          onDelete={() => onDelete?.(contextMenuContract)}
+          onGoToSupervisor={() => onGoToSupervisor?.(contextMenuContract)}
         />
-      ))}
-    </div>
+      )}
+    </>
   );
 }
-- 
cgit v1.2.3

