"use client"; import { IconGitCommit, IconArrowBackUp, IconPencil, IconHistoryToggle, IconArrowUp, } from "@tabler/icons-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from "@kandev/ui/context-menu"; export type CommitItem = { commit_sha: string; commit_message: string; insertions: number; deletions: number; pushed?: boolean; /** Multi-repo: name of the repo this commit was made in. Empty for single-repo. */ repository_name?: string; }; /** Context menu for commit items */ function CommitContextMenu({ children, commit, isLatest, onAmendCommit, onRevertCommit, onResetToCommit, }: { children: React.ReactNode; commit: CommitItem; isLatest: boolean; // Multi-repo: handlers receive the commit's repository_name so the // amend/revert/reset op runs in the right git repo. Without it, ops hit // the workspace root which fails on multi-repo task workspaces. onAmendCommit?: (currentMessage: string, repo?: string) => void; onRevertCommit?: (sha: string, repo?: string) => void; onResetToCommit?: (sha: string, repo?: string) => void; }) { const hasActions = onAmendCommit || onRevertCommit || onResetToCommit; if (!hasActions) { return <>{children}; } return ( {children} {isLatest && onAmendCommit && ( onAmendCommit(commit.commit_message, commit.repository_name)} > Amend message )} {isLatest && onRevertCommit && ( onRevertCommit(commit.commit_sha, commit.repository_name)} > Revert commit )} {onResetToCommit && ( onResetToCommit(commit.commit_sha, commit.repository_name)} > Reset to this commit )} ); } /** Hover action buttons for a commit row */ function CommitRowActions({ commit, isLatest, onAmendCommit, onRevertCommit, onResetToCommit, }: { commit: CommitItem; isLatest: boolean; // Multi-repo: handlers receive the commit's repository_name so the // amend/revert/reset op runs in the right git repo. Without it, ops hit // the workspace root which fails on multi-repo task workspaces. onAmendCommit?: (currentMessage: string, repo?: string) => void; onRevertCommit?: (sha: string, repo?: string) => void; onResetToCommit?: (sha: string, repo?: string) => void; }) { return ( {isLatest && onAmendCommit && ( Amend commit message )} {isLatest && onRevertCommit && ( Revert commit )} {onResetToCommit && ( Reset to this commit )} ); } /** Individual commit row with hover actions */ export function CommitRow({ commit, isLatest, onOpenCommitDetail, onAmendCommit, onRevertCommit, onResetToCommit, }: { commit: CommitItem; isLatest: boolean; // Multi-repo: opening the diff for a non-primary repo's commit needs the // repo subpath, otherwise the agentctl looks up the SHA at the workspace // root and finds nothing (each repo has its own commit graph). onOpenCommitDetail?: (sha: string, repo?: string) => void; // Multi-repo: handlers receive the commit's repository_name so the // amend/revert/reset op runs in the right git repo. Without it, ops hit // the workspace root which fails on multi-repo task workspaces. onAmendCommit?: (currentMessage: string, repo?: string) => void; onRevertCommit?: (sha: string, repo?: string) => void; onResetToCommit?: (sha: string, repo?: string) => void; }) { const showActions = onResetToCommit || (isLatest && (onAmendCommit || onRevertCommit)); return (
  • onOpenCommitDetail?.(commit.commit_sha, commit.repository_name)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpenCommitDetail?.(commit.commit_sha, commit.repository_name); } }} > {commit.pushed === true ? "Pushed commit" : "Unpushed commit"} {commit.pushed === true ? ( {commit.commit_sha.slice(0, 7)} {commit.commit_message} +{commit.insertions}{" "} -{commit.deletions} {showActions && ( )}
  • ); }