"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 && (
)}
);
}