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 --- makima/frontend/src/components/NavStrip.tsx | 1 + .../src/components/contracts/RepositoryPanel.tsx | 2 +- .../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 + makima/frontend/src/lib/api.ts | 399 +++++++++++++++++++++ makima/frontend/src/main.tsx | 17 + makima/frontend/src/routes/contracts.tsx | 2 +- makima/frontend/src/routes/history.tsx | 325 +++++++++++++++++ makima/frontend/tsconfig.tsbuildinfo | 2 +- 16 files changed, 1958 insertions(+), 3 deletions(-) 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 create mode 100644 makima/frontend/src/routes/history.tsx (limited to 'makima/frontend') diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index 48abe09..7e12c75 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -14,6 +14,7 @@ const NAV_LINKS: NavLink[] = [ { label: "Contracts", href: "/contracts", requiresAuth: true }, { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, + { label: "History", href: "/history", requiresAuth: true }, ]; export function NavStrip() { diff --git a/makima/frontend/src/components/contracts/RepositoryPanel.tsx b/makima/frontend/src/components/contracts/RepositoryPanel.tsx index e314140..15741a8 100644 --- a/makima/frontend/src/components/contracts/RepositoryPanel.tsx +++ b/makima/frontend/src/components/contracts/RepositoryPanel.tsx @@ -226,7 +226,7 @@ export function RepositoryPanel({
- {suggestion.repositoryUrl || suggestion.localPath} + {addMode === "local" ? suggestion.localPath : suggestion.repositoryUrl}
))} 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" + /> +
+
+ +