From 205ab8a223ddf6591a3e8bfc9108506502977c11 Mon Sep 17 00:00:00 2001 From: soryu Date: Fri, 16 Jan 2026 12:23:49 +0000 Subject: Fixup: use default api.makima.jp URL and fix default branch detection Also add checkpointing/history --- .../src/components/history/CheckpointCard.tsx | 284 +++++++++++++++++++ .../src/components/history/CheckpointList.tsx | 51 ++++ .../src/components/history/ConversationMessage.tsx | 147 ++++++++++ .../src/components/history/ConversationView.tsx | 114 ++++++++ .../src/components/history/HistoryFilters.tsx | 84 ++++++ .../src/components/history/ResumeControls.tsx | 306 +++++++++++++++++++++ .../src/components/history/TimelineEventCard.tsx | 139 ++++++++++ .../src/components/history/TimelineList.tsx | 80 ++++++ makima/frontend/src/components/history/index.ts | 8 + 9 files changed, 1213 insertions(+) create mode 100644 makima/frontend/src/components/history/CheckpointCard.tsx create mode 100644 makima/frontend/src/components/history/CheckpointList.tsx create mode 100644 makima/frontend/src/components/history/ConversationMessage.tsx create mode 100644 makima/frontend/src/components/history/ConversationView.tsx create mode 100644 makima/frontend/src/components/history/HistoryFilters.tsx create mode 100644 makima/frontend/src/components/history/ResumeControls.tsx create mode 100644 makima/frontend/src/components/history/TimelineEventCard.tsx create mode 100644 makima/frontend/src/components/history/TimelineList.tsx create mode 100644 makima/frontend/src/components/history/index.ts (limited to 'makima/frontend/src/components/history') diff --git a/makima/frontend/src/components/history/CheckpointCard.tsx b/makima/frontend/src/components/history/CheckpointCard.tsx new file mode 100644 index 0000000..fee5bdc --- /dev/null +++ b/makima/frontend/src/components/history/CheckpointCard.tsx @@ -0,0 +1,284 @@ +import { useState } from "react"; +import type { TaskCheckpoint } from "../../lib/api"; +import { forkTask, resumeFromCheckpoint } from "../../lib/api"; + +interface CheckpointCardProps { + checkpoint: TaskCheckpoint; + taskId: string; + onActionComplete: () => void; +} + +export function CheckpointCard({ checkpoint, taskId, onActionComplete }: CheckpointCardProps) { + const [showActions, setShowActions] = useState(false); + const [showForkDialog, setShowForkDialog] = useState(false); + const [showResumeDialog, setShowResumeDialog] = useState(false); + const [forkName, setForkName] = useState(`Fork from checkpoint ${checkpoint.checkpointNumber}`); + const [forkPlan, setForkPlan] = useState(""); + const [resumePlan, setResumePlan] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleFork = async () => { + if (!forkName.trim() || !forkPlan.trim()) { + setError("Name and plan are required"); + return; + } + + setIsLoading(true); + setError(null); + try { + await forkTask(taskId, { + forkFromType: "checkpoint", + forkFromValue: String(checkpoint.checkpointNumber), + newTaskName: forkName, + newTaskPlan: forkPlan, + includeConversation: true, + }); + setShowForkDialog(false); + onActionComplete(); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to fork task"); + } finally { + setIsLoading(false); + } + }; + + const handleResume = async () => { + if (!resumePlan.trim()) { + setError("Plan is required"); + return; + } + + setIsLoading(true); + setError(null); + try { + await resumeFromCheckpoint(taskId, checkpoint.id, { + plan: resumePlan, + includeConversation: true, + }); + setShowResumeDialog(false); + onActionComplete(); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to resume from checkpoint"); + } finally { + setIsLoading(false); + } + }; + + return ( + <> +
+
+ {/* Checkpoint info */} +
+
+ #{checkpoint.checkpointNumber} + + {checkpoint.commitSha.slice(0, 7)} + + + on {checkpoint.branchName} + +
+ + {checkpoint.message && ( +
{checkpoint.message}
+ )} + + {/* Files changed */} + {checkpoint.filesChanged.length > 0 && ( +
+ {checkpoint.filesChanged.slice(0, 5).map((file, i) => ( + + {file.action} {file.path.split("/").pop()} + + ))} + {checkpoint.filesChanged.length > 5 && ( + + +{checkpoint.filesChanged.length - 5} more + + )} +
+ )} + + {/* Stats */} +
+ +{checkpoint.linesAdded} + -{checkpoint.linesRemoved} + {new Date(checkpoint.createdAt).toLocaleString()} +
+
+ + {/* Actions button */} + +
+ + {/* Actions dropdown */} + {showActions && ( +
+ + +
+ )} +
+ + {/* Fork dialog */} + {showForkDialog && ( +
+
+
+

+ Fork from Checkpoint #{checkpoint.checkpointNumber} +

+ +
+
+ {error && ( +
+ {error} +
+ )} +
+ + setForkName(e.target.value)} + className="w-full font-mono text-xs text-[#9bc3ff] bg-[#0a1525] border border-[rgba(117,170,252,0.25)] px-3 py-2 focus:border-[#3f6fb3] focus:outline-none" + /> +
+
+ +