From bdbdb1382888dd0c6dde05d5fa5cc72d889a480d Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Wed, 6 May 2026 10:03:13 -0700 Subject: [PATCH] feat(code): add diff stats to convo view --- .../code-review/components/DiffStatsBadge.tsx | 68 +------------------ .../code-review/hooks/useTaskDiffStats.ts | 57 ++++++++++++++++ .../components/CommandCenterSessionView.tsx | 1 + .../sessions/components/ConversationView.tsx | 4 ++ .../sessions/components/DiffStatsChip.tsx | 58 ++++++++++++++++ .../sessions/components/SessionFooter.tsx | 16 ++++- .../sessions/components/SessionView.tsx | 6 +- .../task-detail/components/TaskLogsPanel.tsx | 1 + 8 files changed, 141 insertions(+), 70 deletions(-) create mode 100644 apps/code/src/renderer/features/code-review/hooks/useTaskDiffStats.ts create mode 100644 apps/code/src/renderer/features/sessions/components/DiffStatsChip.tsx diff --git a/apps/code/src/renderer/features/code-review/components/DiffStatsBadge.tsx b/apps/code/src/renderer/features/code-review/components/DiffStatsBadge.tsx index 417f9d048..655fa0726 100644 --- a/apps/code/src/renderer/features/code-review/components/DiffStatsBadge.tsx +++ b/apps/code/src/renderer/features/code-review/components/DiffStatsBadge.tsx @@ -1,15 +1,4 @@ import { Tooltip } from "@components/ui/Tooltip"; -import { - useLocalBranchChangedFiles, - usePrChangedFiles, -} from "@features/git-interaction/hooks/useGitQueries"; -import { - computeDiffStats, - type DiffStats, -} from "@features/git-interaction/utils/diffStats"; -import { useCwd } from "@features/sidebar/hooks/useCwd"; -import { useCloudChangedFiles } from "@features/task-detail/hooks/useCloudChangedFiles"; -import { useWorkspace } from "@features/workspace/hooks/useWorkspace"; import { GitDiff } from "@phosphor-icons/react"; import { Button } from "@posthog/quill"; import { Flex, Text } from "@radix-ui/themes"; @@ -19,74 +8,21 @@ import { } from "@renderer/constants/keyboard-shortcuts"; import { useReviewNavigationStore } from "@renderer/features/code-review/stores/reviewNavigationStore"; import type { Task } from "@shared/types"; -import { useMemo } from "react"; -import { useEffectiveDiffSource } from "../hooks/useEffectiveDiffSource"; +import { useTaskDiffStats } from "../hooks/useTaskDiffStats"; interface DiffStatsBadgeProps { task: Task; } export function DiffStatsBadge({ task }: DiffStatsBadgeProps) { - const workspace = useWorkspace(task.id); - const isCloud = - workspace?.mode === "cloud" || task.latest_run?.environment === "cloud"; - return isCloud ? ( - - ) : ( - - ); -} - -function CloudDiffStatsBadge({ task }: { task: Task }) { - const { reviewFiles } = useCloudChangedFiles(task.id, task); - const stats = useMemo(() => computeDiffStats(reviewFiles), [reviewFiles]); - return ; -} - -function LocalDiffStatsBadge({ task }: { task: Task }) { const taskId = task.id; - const repoPath = useCwd(taskId); - const { - effectiveSource, - linkedBranch, - prUrl, - diffStats: localDiffStats, - } = useEffectiveDiffSource(taskId); - - const { data: branchFiles } = useLocalBranchChangedFiles( - effectiveSource === "branch" ? (repoPath ?? null) : null, - effectiveSource === "branch" ? linkedBranch : null, - ); - const { data: prFiles } = usePrChangedFiles( - effectiveSource === "pr" ? prUrl : null, - ); - - const stats = useMemo(() => { - if (effectiveSource === "branch" && branchFiles) { - return computeDiffStats(branchFiles); - } - if (effectiveSource === "pr" && prFiles) { - return computeDiffStats(prFiles); - } - return localDiffStats; - }, [effectiveSource, branchFiles, prFiles, localDiffStats]); - - return ; -} + const { filesChanged, linesAdded, linesRemoved } = useTaskDiffStats(task); -function DiffStatsButton({ - taskId, - stats, -}: { - taskId: string; - stats: DiffStats; -}) { const reviewMode = useReviewNavigationStore( (s) => s.reviewModes[taskId] ?? "closed", ); const setReviewMode = useReviewNavigationStore((s) => s.setReviewMode); - const { filesChanged, linesAdded, linesRemoved } = stats; const hasChanges = filesChanged > 0; const isOpen = reviewMode !== "closed"; diff --git a/apps/code/src/renderer/features/code-review/hooks/useTaskDiffStats.ts b/apps/code/src/renderer/features/code-review/hooks/useTaskDiffStats.ts new file mode 100644 index 000000000..c055739e7 --- /dev/null +++ b/apps/code/src/renderer/features/code-review/hooks/useTaskDiffStats.ts @@ -0,0 +1,57 @@ +import { + useLocalBranchChangedFiles, + usePrChangedFiles, +} from "@features/git-interaction/hooks/useGitQueries"; +import { + computeDiffStats, + type DiffStats, +} from "@features/git-interaction/utils/diffStats"; +import { useCwd } from "@features/sidebar/hooks/useCwd"; +import { useCloudChangedFiles } from "@features/task-detail/hooks/useCloudChangedFiles"; +import { useWorkspace } from "@features/workspace/hooks/useWorkspace"; +import type { Task } from "@shared/types"; +import { useMemo } from "react"; +import { useEffectiveDiffSource } from "./useEffectiveDiffSource"; + +export function useTaskDiffStats(task: Task): DiffStats { + const taskId = task.id; + const workspace = useWorkspace(taskId); + const isCloud = + workspace?.mode === "cloud" || task.latest_run?.environment === "cloud"; + + const { reviewFiles } = useCloudChangedFiles(taskId, task, isCloud); + + const repoPath = useCwd(taskId); + const { + effectiveSource, + linkedBranch, + prUrl, + diffStats: localDiffStats, + } = useEffectiveDiffSource(taskId); + + const { data: branchFiles } = useLocalBranchChangedFiles( + !isCloud && effectiveSource === "branch" ? (repoPath ?? null) : null, + !isCloud && effectiveSource === "branch" ? linkedBranch : null, + ); + const { data: prFiles } = usePrChangedFiles( + !isCloud && effectiveSource === "pr" ? prUrl : null, + ); + + return useMemo(() => { + if (isCloud) return computeDiffStats(reviewFiles); + if (effectiveSource === "branch" && branchFiles) { + return computeDiffStats(branchFiles); + } + if (effectiveSource === "pr" && prFiles) { + return computeDiffStats(prFiles); + } + return localDiffStats; + }, [ + isCloud, + reviewFiles, + effectiveSource, + branchFiles, + prFiles, + localDiffStats, + ]); +} diff --git a/apps/code/src/renderer/features/command-center/components/CommandCenterSessionView.tsx b/apps/code/src/renderer/features/command-center/components/CommandCenterSessionView.tsx index 6ffd0050d..44791e68b 100644 --- a/apps/code/src/renderer/features/command-center/components/CommandCenterSessionView.tsx +++ b/apps/code/src/renderer/features/command-center/components/CommandCenterSessionView.tsx @@ -55,6 +55,7 @@ export function CommandCenterSessionView({ s.reviewModes[taskId] ?? "closed", + ); + const setReviewMode = useReviewNavigationStore((s) => s.setReviewMode); + + if (filesChanged === 0) return null; + + const isOpen = reviewMode !== "closed"; + + const handleClick = () => { + setReviewMode(taskId, isOpen ? "closed" : "expanded"); + }; + + return ( + + + + + {filesChanged} {filesChanged === 1 ? "file" : "files"} + + {linesAdded > 0 && ( + +{linesAdded} + )} + {linesRemoved > 0 && ( + -{linesRemoved} + )} + + + ); +} diff --git a/apps/code/src/renderer/features/sessions/components/SessionFooter.tsx b/apps/code/src/renderer/features/sessions/components/SessionFooter.tsx index 950371ac3..daa0cbb05 100644 --- a/apps/code/src/renderer/features/sessions/components/SessionFooter.tsx +++ b/apps/code/src/renderer/features/sessions/components/SessionFooter.tsx @@ -1,11 +1,14 @@ import type { ContextUsage } from "@features/sessions/hooks/useContextUsage"; import { Brain, Pause } from "@phosphor-icons/react"; import { Box, Flex, Text } from "@radix-ui/themes"; +import type { Task } from "@shared/types"; import { ContextUsageIndicator } from "./ContextUsageIndicator"; +import { DiffStatsChip } from "./DiffStatsChip"; import { formatDuration, GeneratingIndicator } from "./GeneratingIndicator"; interface SessionFooterProps { + task?: Task; isPromptPending: boolean | null; promptStartedAt?: number | null; lastGenerationDuration: number | null; @@ -18,6 +21,7 @@ interface SessionFooterProps { } export function SessionFooter({ + task, isPromptPending, promptStartedAt, lastGenerationDuration, @@ -28,6 +32,12 @@ export function SessionFooter({ isCompacting = false, usage, }: SessionFooterProps) { + const rightSide = ( + + {task && } + + + ); if (isPromptPending && !isCompacting) { if (hasPendingPermission) { return ( @@ -42,7 +52,7 @@ export function SessionFooter({ Awaiting permission... - + {rightSide} ); @@ -62,7 +72,7 @@ export function SessionFooter({ )} - + {rightSide} ); @@ -91,7 +101,7 @@ export function SessionFooter({ )} - + {rightSide} ); diff --git a/apps/code/src/renderer/features/sessions/components/SessionView.tsx b/apps/code/src/renderer/features/sessions/components/SessionView.tsx index 456f3b277..c542a1fcc 100644 --- a/apps/code/src/renderer/features/sessions/components/SessionView.tsx +++ b/apps/code/src/renderer/features/sessions/components/SessionView.tsx @@ -21,7 +21,7 @@ import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping"; import { Pause, Spinner, Warning } from "@phosphor-icons/react"; import { Box, Button, ContextMenu, Flex, Text } from "@radix-ui/themes"; import { toast } from "@renderer/utils/toast"; -import type { TaskRunStatus } from "@shared/types"; +import type { Task, TaskRunStatus } from "@shared/types"; import { type AcpMessage, isJsonRpcNotification, @@ -45,6 +45,7 @@ import { RawLogsView } from "./raw-logs/RawLogsView"; interface SessionViewProps { events: AcpMessage[]; taskId?: string; + task?: Task; isRunning: boolean; isPromptPending?: boolean | null; promptStartedAt?: number | null; @@ -97,6 +98,7 @@ function resolveAllowAlwaysUpgradeMode( export function SessionView({ events, taskId, + task, isRunning, isPromptPending = false, promptStartedAt, @@ -443,6 +445,7 @@ export function SessionView({ promptStartedAt={promptStartedAt} repoPath={repoPath} taskId={taskId} + task={task} slackThreadUrl={slackThreadUrl} /> @@ -513,6 +516,7 @@ export function SessionView({ promptStartedAt={promptStartedAt} repoPath={repoPath} taskId={taskId} + task={task} slackThreadUrl={slackThreadUrl} compact={compact} /> diff --git a/apps/code/src/renderer/features/task-detail/components/TaskLogsPanel.tsx b/apps/code/src/renderer/features/task-detail/components/TaskLogsPanel.tsx index a12fb4c5e..38ec43c01 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskLogsPanel.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskLogsPanel.tsx @@ -127,6 +127,7 @@ export function TaskLogsPanel({ taskId, task, hideInput }: TaskLogsPanelProps) {