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
---
 .../src/components/mesh/GitActionsPanel.tsx        | 40 +++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

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

diff --git a/makima/frontend/src/components/mesh/GitActionsPanel.tsx b/makima/frontend/src/components/mesh/GitActionsPanel.tsx
index be2e06d..ff1dc7d 100644
--- a/makima/frontend/src/components/mesh/GitActionsPanel.tsx
+++ b/makima/frontend/src/components/mesh/GitActionsPanel.tsx
@@ -1,5 +1,5 @@
 import { useState, useCallback } from "react";
-import { exportTaskPatch, pushTaskBranch, createTaskPR, type ExportPatchResponse } from "../../lib/api";
+import { exportTaskPatch, pushTaskBranch, createTaskPR, commitWorktree, type ExportPatchResponse } from "../../lib/api";
 
 interface GitActionsPanelProps {
   taskId: string;
@@ -20,6 +20,8 @@ export function GitActionsPanel({
   const [isExporting, setIsExporting] = useState(false);
   const [isPushing, setIsPushing] = useState(false);
   const [isCreatingPR, setIsCreatingPR] = useState(false);
+  const [isCommitting, setIsCommitting] = useState(false);
+  const [commitMessage, setCommitMessage] = useState("");
   const [toast, setToast] = useState<ToastMessage | null>(null);
   const [exportedPatch, setExportedPatch] = useState<ExportPatchResponse | null>(null);
 
@@ -82,6 +84,23 @@ export function GitActionsPanel({
     }
   }, [taskId, isLocalOnly]);
 
+  const handleCommit = useCallback(async () => {
+    setIsCommitting(true);
+    try {
+      const result = await commitWorktree(taskId, commitMessage || undefined);
+      if (result.success) {
+        showToast("success", `Committed: ${result.commitSha?.substring(0, 8) || "done"}`);
+        setCommitMessage("");
+      } else {
+        showToast("error", result.error || "Failed to commit");
+      }
+    } catch (e) {
+      showToast("error", e instanceof Error ? e.message : "Failed to commit");
+    } finally {
+      setIsCommitting(false);
+    }
+  }, [taskId, commitMessage]);
+
   return (
     <div className="space-y-3">
       {/* Section Header */}
@@ -112,6 +131,25 @@ export function GitActionsPanel({
         </div>
       )}
 
+      {/* Commit section */}
+      <div className="flex gap-2">
+        <input
+          type="text"
+          value={commitMessage}
+          onChange={(e) => setCommitMessage(e.target.value)}
+          placeholder="Commit message (optional)"
+          className="flex-1 px-2 py-1.5 font-mono text-xs bg-transparent border border-[rgba(117,170,252,0.2)] text-[#dbe7ff] placeholder:text-[#555] focus:border-[#75aafc] focus:outline-none"
+          onKeyDown={(e) => { if (e.key === "Enter") handleCommit(); }}
+        />
+        <button
+          onClick={handleCommit}
+          disabled={isCommitting}
+          className="px-3 py-1.5 font-mono text-xs text-emerald-400 border border-emerald-400/30 hover:border-emerald-400/50 hover:bg-emerald-400/10 transition-colors disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
+        >
+          {isCommitting ? "Committing..." : "Commit"}
+        </button>
+      </div>
+
       {/* Action buttons */}
       <div className="flex flex-wrap gap-2">
         <button
-- 
cgit v1.2.3

