"use client"; import { memo, useEffect, useMemo } from "react"; import { IconAlertCircle, IconLoader2, IconRefresh } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { useTranslation } from "react-i18next"; import { PanelRoot, PanelBody } from "./panel-primitives"; import { FileDiffViewer } from "@/components/diff"; import { DEFAULT_DIFF_WORD_WRAP } from "@/components/diff/diff-defaults"; import { useAppStore } from "@/components/state-provider"; import { useSessionCommits } from "@/hooks/domains/session/use-session-commits"; import { useCommitDetail } from "@/hooks/domains/session/use-commit-detail"; import { usePanelActions } from "@/hooks/use-panel-actions"; import { setPanelTitle } from "@/lib/layout/panel-portal-manager"; import { formatRelativeTime } from "@/lib/utils"; import type { FileInfo } from "@/lib/state/store"; import type { CommitDetailTarget } from "./changes-diff-target"; type CommitDetailPanelProps = { panelId: string; params: Record; }; type CommitDiffViewProps = { target: CommitDetailTarget; onOpenFile?: (path: string) => void; wordWrap?: boolean; }; function isCommitDetailTarget(value: unknown): value is CommitDetailTarget { if (!value || typeof value !== "object") return false; const target = value as { source?: unknown; sha?: unknown; repo?: unknown; workspaceId?: unknown; owner?: unknown; }; if (typeof target.sha !== "string" || target.sha.length === 0) return false; if (target.source === "local") { return target.repo === undefined || typeof target.repo === "string"; } return ( target.source === "github" && typeof target.workspaceId === "string" && target.workspaceId.length > 0 && typeof target.owner === "string" && target.owner.length > 0 && typeof target.repo === "string" && target.repo.length > 0 ); } function targetFromParams(params: Record): CommitDetailTarget { if (isCommitDetailTarget(params.target)) return params.target; return { source: "local", sha: typeof params.commitSha === "string" ? params.commitSha : "", ...(typeof params.repo === "string" && params.repo ? { repo: params.repo } : {}), }; } function getInitials(name: string): string { return name .split(/\s+/) .map((w) => w[0]) .filter(Boolean) .slice(0, 2) .join("") .toUpperCase(); } function useSortedFileEntries(files: Record | null): [string, FileInfo][] { return useMemo(() => { if (!files) return []; return Object.entries(files).sort(([a], [b]) => a.localeCompare(b)); }, [files]); } function useActiveCommit(target: CommitDetailTarget) { const activeSessionId = useAppStore((state) => state.tasks.activeSessionId); const { commits } = useSessionCommits(activeSessionId ?? null); return useMemo(() => { if (target.source !== "local") return undefined; return commits.find( (c) => c.commit_sha === target.sha && (!target.repo || !c.repository_name || c.repository_name === target.repo), ); }, [commits, target]); } function headerCommit( target: CommitDetailTarget, localCommit: ReturnType, remoteCommit: ReturnType["commit"], ) { if (target.source === "github" && remoteCommit) { return { author_name: remoteCommit.author_name || remoteCommit.author_login, commit_message: remoteCommit.message, committed_at: remoteCommit.author_date, }; } return localCommit; } /** Standalone commit diff viewer — no dockview dependencies. */ export const CommitDiffView = memo(function CommitDiffView({ target, onOpenFile, wordWrap = DEFAULT_DIFF_WORD_WRAP, }: CommitDiffViewProps) { const { t } = useTranslation(); const localCommit = useActiveCommit(target); const { files, commit: remoteCommit, loading, error, refetch } = useCommitDetail(target); const commit = headerCommit(target, localCommit, remoteCommit); const fileEntries = useSortedFileEntries(files); if (loading) { return (
{t("task:loadingCommit")}
); } if (error) return ; return (
{commit && }
); }); const CommitDetailPanel = memo(function CommitDetailPanel({ panelId, params, }: CommitDetailPanelProps) { const { t } = useTranslation(); const target = targetFromParams(params); const { openFile } = usePanelActions(); const localCommit = useActiveCommit(target); const { files, commit: remoteCommit, loading, error, refetch } = useCommitDetail(target); const commit = headerCommit(target, localCommit, remoteCommit); const fileEntries = useSortedFileEntries(files); // Update tab title via dockview API stored in portal manager useEffect(() => { if (commit) { const shortSha = target.sha.slice(0, 7); const msg = commit.commit_message.length > 30 ? commit.commit_message.slice(0, 30) + "..." : commit.commit_message; setPanelTitle(panelId, `${shortSha} ${msg}`); } }, [commit, panelId, target.sha]); if (loading) { return (
{t("task:loadingCommit")}
); } if (error) { return ( ); } return (
{commit && }
); }); /** Commit metadata header with author and message */ function CommitHeader({ commit, commitSha, }: { commit: { author_name: string; commit_message: string; committed_at: string }; commitSha: string; }) { return (
{getInitials(commit.author_name)}

{commit.commit_message}

{commit.author_name} · {formatRelativeTime(commit.committed_at)} · {commitSha.slice(0, 7)}

); } function CommitDetailErrorState({ error, onRetry, }: { error: string; onRetry: () => Promise; }) { const { t } = useTranslation(); return (
{error}
); } /** List of file diffs in a commit */ function CommitFileList({ fileEntries, loading, onOpenFile, baseRef, repo, remote, wordWrap, }: { fileEntries: [string, FileInfo][]; loading: boolean; onOpenFile?: (path: string) => void; baseRef?: string; repo?: string; remote: boolean; wordWrap?: boolean; }) { const { t } = useTranslation(); if (fileEntries.length === 0 && !loading) { return (
{t("task:noFilesInThisCommit")}
); } return ( <> {fileEntries.map(([path, file]) => (
{file.diff ? ( ) : (
{t("task:binaryOrEmptyDiff", { path })}
)}
))} ); } export { CommitDetailPanel };