"use client"; import { memo, useEffect, useMemo } from "react"; import { IconLoader2 } from "@tabler/icons-react"; import { PanelRoot, PanelBody } from "./panel-primitives"; import { FileDiffViewer } from "@/components/diff"; import { useAppStore } from "@/components/state-provider"; import { useSessionCommits } from "@/hooks/domains/session/use-session-commits"; import { useCommitDiff } from "@/hooks/domains/session/use-commit-diff"; import { usePanelActions } from "@/hooks/use-panel-actions"; import { setPanelTitle } from "@/lib/layout/panel-portal-manager"; import type { FileInfo } from "@/lib/state/store"; type CommitDetailPanelProps = { panelId: string; params: Record; }; type CommitDiffViewProps = { sha: string; repo?: string; onOpenFile?: (path: string) => void; wordWrap?: boolean; }; function formatRelativeTime(dateStr: string): string { const date = new Date(dateStr); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMin = Math.floor(diffMs / 60000); if (diffMin < 1) return "just now"; if (diffMin < 60) return `${diffMin} minute${diffMin !== 1 ? "s" : ""} ago`; const diffHours = Math.floor(diffMin / 60); if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`; const diffDays = Math.floor(diffHours / 24); if (diffDays < 30) return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`; return date.toLocaleDateString(); } 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(commitSha: string) { const activeSessionId = useAppStore((state) => state.tasks.activeSessionId); const { commits } = useSessionCommits(activeSessionId ?? null); return useMemo(() => commits.find((c) => c.commit_sha === commitSha), [commits, commitSha]); } /** Standalone commit diff viewer — no dockview dependencies. */ export const CommitDiffView = memo(function CommitDiffView({ sha: commitSha, repo, onOpenFile, wordWrap, }: CommitDiffViewProps) { const commit = useActiveCommit(commitSha); const { files, loading } = useCommitDiff(commitSha, repo); const fileEntries = useSortedFileEntries(files); if (loading) { return (
Loading commit...
); } return (
{commit && }
{})} baseRef={`${commitSha}^`} repo={repo} wordWrap={wordWrap} />
); }); const CommitDetailPanel = memo(function CommitDetailPanel({ panelId, params, }: CommitDetailPanelProps) { const commitSha = params.commitSha as string; const repo = (params.repo as string | undefined) ?? undefined; const { openFile } = usePanelActions(); const commit = useActiveCommit(commitSha); const { files, loading } = useCommitDiff(commitSha, repo); const fileEntries = useSortedFileEntries(files); // Update tab title via dockview API stored in portal manager useEffect(() => { if (commit) { const shortSha = commitSha.slice(0, 7); const msg = commit.commit_message.length > 30 ? commit.commit_message.slice(0, 30) + "..." : commit.commit_message; setPanelTitle(panelId, `${shortSha} ${msg}`); } }, [commit, commitSha, panelId]); if (loading) { return (
Loading commit...
); } 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)}

); } /** List of file diffs in a commit */ function CommitFileList({ fileEntries, loading, onOpenFile, baseRef, repo, wordWrap, }: { fileEntries: [string, FileInfo][]; loading: boolean; onOpenFile: (path: string) => void; baseRef: string; repo?: string; wordWrap?: boolean; }) { if (fileEntries.length === 0 && !loading) { return (
No files in this commit
); } return ( <> {fileEntries.map(([path, file]) => (
{file.diff ? ( ) : (
{path} -- binary or empty diff
)}
))} ); } export { CommitDetailPanel };