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/components/mesh/TaskDetail.tsx | 41 ++++++++++++++++++++--
 1 file changed, 39 insertions(+), 2 deletions(-)

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

diff --git a/makima/frontend/src/components/mesh/TaskDetail.tsx b/makima/frontend/src/components/mesh/TaskDetail.tsx
index fdcc58b..00719e2 100644
--- a/makima/frontend/src/components/mesh/TaskDetail.tsx
+++ b/makima/frontend/src/components/mesh/TaskDetail.tsx
@@ -1,6 +1,6 @@
 import { useState, useCallback, useMemo, useEffect } from "react";
 import type { TaskWithSubtasks, TaskStatus, TaskSummary, CompletionAction, DaemonDirectory } from "../../lib/api";
-import { retryCompletionAction, getDaemonDirectories, cloneWorktree } from "../../lib/api";
+import { retryCompletionAction, getDaemonDirectories, cloneWorktree, getWorktreeDiff } from "../../lib/api";
 import { SubtaskTree, SubtaskProgressBar, calculateTreeStats } from "./SubtaskTree";
 import { OverlayDiffViewer } from "./OverlayDiffViewer";
 import { PRPreview } from "./PRPreview";
@@ -136,6 +136,9 @@ export function TaskDetail({
   );
   const [showDiff, setShowDiff] = useState(false);
   const [showPRPreview, setShowPRPreview] = useState(false);
+  const [worktreeFileDiff, setWorktreeFileDiff] = useState<string | null>(null);
+  const [worktreeFileDiffPath, setWorktreeFileDiffPath] = useState<string | null>(null);
+  const [worktreeFileDiffLoading, setWorktreeFileDiffLoading] = useState(false);
   const [useTreeView, setUseTreeView] = useState(false);
   // Track which subtask is expanded for inline editing
   const [expandedSubtaskId, setExpandedSubtaskId] = useState<string | null>(null);
@@ -153,6 +156,22 @@ export function TaskDetail({
   // Track branch modal state
   const [showBranchModal, setShowBranchModal] = useState(false);
 
+  // Handle clicking a file in the worktree panel to show its diff
+  const handleWorktreeFileClick = useCallback(async (filePath: string) => {
+    setWorktreeFileDiffLoading(true);
+    setWorktreeFileDiffPath(filePath);
+    setWorktreeFileDiff(null);
+    try {
+      const result = await getWorktreeDiff(task.id, filePath);
+      setWorktreeFileDiff(result.diff);
+    } catch (e) {
+      console.error("Failed to fetch worktree diff:", e);
+      setWorktreeFileDiff("");
+    } finally {
+      setWorktreeFileDiffLoading(false);
+    }
+  }, [task.id]);
+
   // Check if task is running
   const isTaskRunning = task.status === "running" || task.status === "initializing" || task.status === "starting";
   // Check if task is in a terminal state (can be continued/reopened)
@@ -897,7 +916,7 @@ export function TaskDetail({
 
         {/* Worktree Files Panel - show changed files in the worktree */}
         {(task.status === "done" || task.status === "failed" || task.status === "merged" || task.status === "running") && (
-          <WorktreeFilesPanel taskId={task.id} />
+          <WorktreeFilesPanel taskId={task.id} onFileClick={handleWorktreeFileClick} />
         )}
 
         {/* Patches List Panel - show exported patches for this task */}
@@ -920,6 +939,24 @@ export function TaskDetail({
         </div>
       )}
 
+      {/* Worktree File Diff Modal */}
+      {(worktreeFileDiff !== null || worktreeFileDiffLoading) && (
+        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
+          <div className="max-w-4xl w-full max-h-[80vh]">
+            <OverlayDiffViewer
+              diff={worktreeFileDiff || ""}
+              loading={worktreeFileDiffLoading}
+              onClose={() => {
+                setWorktreeFileDiff(null);
+                setWorktreeFileDiffPath(null);
+                setWorktreeFileDiffLoading(false);
+              }}
+              title={worktreeFileDiffPath ? `Diff: ${worktreeFileDiffPath}` : "File Diff"}
+            />
+          </div>
+        </div>
+      )}
+
       {/* PR Preview Modal */}
       {showPRPreview && (
         <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
-- 
cgit v1.2.3

