From f49aaa39a32661b54c109ba002d24cbdf73f4ea3 Mon Sep 17 00:00:00 2001 From: soryu 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 --- .../components/directives/TaskSlideOutPanel.tsx | 145 +++++++++++++++++---- 1 file changed, 122 insertions(+), 23 deletions(-) (limited to 'makima/frontend/src/components/directives/TaskSlideOutPanel.tsx') diff --git a/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx b/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx index 29fce23..176728c 100644 --- a/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx +++ b/makima/frontend/src/components/directives/TaskSlideOutPanel.tsx @@ -3,7 +3,8 @@ import { useTaskSubscription } from "../../hooks/useTaskSubscription"; import type { TaskOutputEvent } from "../../hooks/useTaskSubscription"; import { TaskOutput } from "../mesh/TaskOutput"; import { WorktreeFilesPanel } from "../mesh/WorktreeFilesPanel"; -import { getTaskOutput } from "../../lib/api"; +import { OverlayDiffViewer } from "../mesh/OverlayDiffViewer"; +import { getTaskOutput, getTaskDiff } from "../../lib/api"; interface TaskSlideOutPanelProps { taskId: string; @@ -21,21 +22,37 @@ export function TaskSlideOutPanel({ const [entries, setEntries] = useState([]); const [isStreaming, setIsStreaming] = useState(false); const [loadingHistory, setLoadingHistory] = useState(false); + const [showDiff, setShowDiff] = useState(false); + const [diffContent, setDiffContent] = useState(""); + const [diffLoading, setDiffLoading] = useState(false); // Escape key handler useEffect(() => { const handleEsc = (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); + // If diff is showing, close diff first; otherwise close panel + if (e.key === "Escape") { + if (selectedFileDiff !== null || diffLoading) { + setSelectedFileDiff(null); + setSelectedFilePath(null); + setDiffLoading(false); + } else { + onClose(); + } + } }; if (isOpen) document.addEventListener("keydown", handleEsc); return () => document.removeEventListener("keydown", handleEsc); - }, [isOpen, onClose]); + }, [isOpen, onClose, selectedFileDiff, diffLoading]); // Load historical output when panel opens with a taskId useEffect(() => { if (!isOpen || !taskId) { setEntries([]); setIsStreaming(false); + // Reset diff state when panel closes + setSelectedFileDiff(null); + setSelectedFilePath(null); + setDiffLoading(false); return; } @@ -98,6 +115,23 @@ export function TaskSlideOutPanel({ [] ); + // Handle file click to show diff + const handleFileClick = useCallback(async (_filePath: string) => { + if (!taskId) return; + setDiffLoading(true); + try { + const result = await getTaskDiff(taskId); + if (result.success && result.diff) { + setDiffContent(result.diff); + setShowDiff(true); + } + } catch (e) { + console.error("Failed to get diff:", e); + } finally { + setDiffLoading(false); + } + }, [taskId]); + // Subscribe to live output useTaskSubscription({ taskId: isOpen ? taskId : null, @@ -106,6 +140,8 @@ export function TaskSlideOutPanel({ onUpdate: handleUpdate, }); + const showingDiff = selectedFileDiff !== null || diffLoading; + return ( <> {/* Backdrop overlay */} @@ -125,13 +161,27 @@ export function TaskSlideOutPanel({ {/* Header */}
+ {showingDiff && ( + + )} - Task + {showingDiff ? "Diff" : "Task"} - {taskName || taskId} + {showingDiff ? (selectedFilePath || "Loading...") : (taskName || taskId)} - {isStreaming && ( + {!showingDiff && isStreaming && ( @@ -140,40 +190,89 @@ export function TaskSlideOutPanel({ )}
+
{/* Content */}
- {/* Task Output section (~60% height) */} -
- {loadingHistory ? ( -
- - Loading output... - -
- ) : ( - + { + setSelectedFileDiff(null); + setSelectedFilePath(null); + setDiffLoading(false); + }} + title={selectedFilePath ? `Diff: ${selectedFilePath}` : "File Diff"} /> - )} -
+
+ ) : ( + <> + {/* Task Output section (~60% height) */} +
+ {loadingHistory ? ( +
+ + Loading output... + +
+ ) : ( + + )} +
{/* Worktree Changes section (~40% height) */}
- {taskId && } + {taskId && }
+ + {/* Diff modal */} + {showDiff && ( +
+
+ setShowDiff(false)} + title={`Changes in ${taskName || taskId}`} + /> +
+
+ )} ); } -- cgit v1.2.3