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) {