"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"; import { timeAgo } from "@/lib/utils/time"; import type { CommitDetailTarget } from "./changes-diff-target"; export type CommitItem = { commit_sha: string; commit_message: string; insertions: number; deletions: number; pushed?: boolean; statsAvailable: boolean; detailTarget: CommitDetailTarget; /** Multi-repo: name of the repo this commit was made in. Empty for single-repo. */ repository_name?: string; committed_at?: 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 = commit.detailTarget.source === "local" && (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?: (target: CommitDetailTarget) => 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 isLocalCommit = commit.detailTarget.source === "local"; const showActions = isLocalCommit && (onResetToCommit || (isLatest && (onAmendCommit || onRevertCommit))); return (
  • onOpenCommitDetail?.(commit.detailTarget)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpenCommitDetail?.(commit.detailTarget); } }} > {commit.pushed === true ? "Pushed commit" : "Unpushed commit"} {commit.pushed === true ? ( {commit.commit_sha.slice(0, 7)} {commit.commit_message} {commit.statsAvailable && ( +{commit.insertions}{" "} -{commit.deletions} )} {commit.committed_at && ( {timeAgo(commit.committed_at)} )} {showActions && ( )}
  • ); }