From f49aaa39a32661b54c109ba002d24cbdf73f4ea3 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Mon, 9 Mar 2026 17:20:52 +0000
Subject: feat: worktree diff/commit endpoints and frontend diff viewing (#88)

* feat: soryu-co/soryu - makima: Fix worktree info failing when origin ref is missing

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Add worktree commit endpoint and diff endpoint for regular users

* feat: soryu-co/soryu - makima: Add frontend diff viewing with clickable worktree files
---
 makima/frontend/src/routes/mesh.tsx | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

(limited to 'makima/frontend/src/routes/mesh.tsx')

diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx
index 67129f9..f210227 100644
--- a/makima/frontend/src/routes/mesh.tsx
+++ b/makima/frontend/src/routes/mesh.tsx
@@ -9,7 +9,7 @@ import { ContractCompleteQuestion } from "../components/mesh/ContractCompleteQue
 import { useTasks } from "../hooks/useTasks";
 import { useTaskSubscription, type TaskUpdateEvent, type TaskOutputEvent } from "../hooks/useTaskSubscription";
 import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary, RepositoryHistoryEntry } from "../lib/api";
-import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, continueTask as continueTaskApi, resumeSupervisor, branchTask, getRepositorySuggestions } from "../lib/api";
+import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, continueTask as continueTaskApi, resumeSupervisor, branchTask, getRepositorySuggestions, getTaskDiff } from "../lib/api";
 import { DirectoryInput } from "../components/mesh/DirectoryInput";
 import { useAuth } from "../contexts/AuthContext";
 import { useSupervisorQuestions } from "../contexts/SupervisorQuestionsContext";
@@ -136,6 +136,8 @@ export default function MeshPage() {
   const [viewingSubtaskName, setViewingSubtaskName] = useState<string | null>(null);
   // For supervisor tasks: all tasks in the contract (excluding the supervisor itself)
   const [contractTasks, setContractTasks] = useState<TaskSummary[]>([]);
+  // Overlay diff content for viewing worktree changes
+  const [overlayDiff, setOverlayDiff] = useState<string | undefined>(undefined);
   // View mode for the split panel layout
   const [viewMode, setViewMode] = useState<ViewMode>("split");
   // Width of the task panel as a percentage (0-100)
@@ -297,6 +299,7 @@ export default function MeshPage() {
   useEffect(() => {
     setViewingSubtaskId(null);
     setViewingSubtaskName(null);
+    setOverlayDiff(undefined);
   }, [id]);
 
   // Toggle viewing a subtask's output (for running subtasks)
@@ -315,6 +318,22 @@ export default function MeshPage() {
     [viewingSubtaskId]
   );
 
+  // Request diff for the current task
+  const handleRequestDiff = useCallback(async () => {
+    if (!id) return;
+    try {
+      const result = await getTaskDiff(id);
+      if (result.success && result.diff) {
+        setOverlayDiff(result.diff);
+      } else {
+        setOverlayDiff(result.error || "Failed to get diff");
+      }
+    } catch (e) {
+      console.error("Failed to get diff:", e);
+      setOverlayDiff(e instanceof Error ? e.message : "Failed to get diff");
+    }
+  }, [id]);
+
   // Load task detail when URL has an id
   useEffect(() => {
     if (id) {
@@ -810,6 +829,8 @@ export default function MeshPage() {
                       onViewContract={(contractId) => navigate(`/contracts/${contractId}`)}
                       onBranch={handleBranch}
                       contractTasks={taskDetail.isSupervisor ? contractTasks : undefined}
+                      overlayDiff={overlayDiff}
+                      onRequestDiff={handleRequestDiff}
                     />
                   </div>
                 )}
-- 
cgit v1.2.3

