Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 ? (
<CloudDiffStatsBadge task={task} />
) : (
<LocalDiffStatsBadge task={task} />
);
}

function CloudDiffStatsBadge({ task }: { task: Task }) {
const { reviewFiles } = useCloudChangedFiles(task.id, task);
const stats = useMemo(() => computeDiffStats(reviewFiles), [reviewFiles]);
return <DiffStatsButton taskId={task.id} stats={stats} />;
}

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<DiffStats>(() => {
if (effectiveSource === "branch" && branchFiles) {
return computeDiffStats(branchFiles);
}
if (effectiveSource === "pr" && prFiles) {
return computeDiffStats(prFiles);
}
return localDiffStats;
}, [effectiveSource, branchFiles, prFiles, localDiffStats]);

return <DiffStatsButton taskId={taskId} stats={stats} />;
}
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";

Expand Down
Original file line number Diff line number Diff line change
@@ -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<DiffStats>(() => {
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,
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function CommandCenterSessionView({
<SessionView
events={events}
taskId={taskId}
task={task}
isRunning={isRunning}
isPromptPending={isPromptPending}
promptStartedAt={promptStartedAt}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ArrowDown, XCircle } from "@phosphor-icons/react";
import { WorkerPoolContextProvider } from "@pierre/diffs/react";
import WorkerUrl from "@pierre/diffs/worker/worker.js?worker&url";
import { Box, Button, Flex, Text } from "@radix-ui/themes";
import type { Task } from "@shared/types";
import type { AcpMessage } from "@shared/types/session-events";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Expand Down Expand Up @@ -54,6 +55,7 @@ interface ConversationViewProps {
promptStartedAt?: number | null;
repoPath?: string | null;
taskId?: string;
task?: Task;
slackThreadUrl?: string;
compact?: boolean;
}
Expand All @@ -64,6 +66,7 @@ export function ConversationView({
promptStartedAt,
repoPath,
taskId,
task,
slackThreadUrl,
compact = false,
}: ConversationViewProps) {
Expand Down Expand Up @@ -277,6 +280,7 @@ export function ConversationView({
footer={
<div className={compact ? "pb-1" : "pb-16"}>
<SessionFooter
task={task}
isPromptPending={isPromptPending}
promptStartedAt={promptStartedAt}
lastGenerationDuration={
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Tooltip } from "@components/ui/Tooltip";
import { useTaskDiffStats } from "@features/code-review/hooks/useTaskDiffStats";
import { useReviewNavigationStore } from "@features/code-review/stores/reviewNavigationStore";
import { GitDiff } from "@phosphor-icons/react";
import { Flex, Text } from "@radix-ui/themes";
import {
formatHotkey,
SHORTCUTS,
} from "@renderer/constants/keyboard-shortcuts";
import type { Task } from "@shared/types";

interface DiffStatsChipProps {
task: Task;
}

export function DiffStatsChip({ task }: DiffStatsChipProps) {
const taskId = task.id;
const { filesChanged, linesAdded, linesRemoved } = useTaskDiffStats(task);

const reviewMode = useReviewNavigationStore(
(s) => 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 (
<Tooltip
content={isOpen ? "Close review" : "Open review"}
shortcut={formatHotkey(SHORTCUTS.TOGGLE_REVIEW_PANEL)}
side="top"
>
<Flex
align="center"
gap="1"
onClick={handleClick}
className="cursor-pointer select-none text-[13px] text-gray-10 tabular-nums hover:text-gray-12"
>
<GitDiff size={12} className="shrink-0" />
<Text className="text-[13px]">
{filesChanged} {filesChanged === 1 ? "file" : "files"}
</Text>
{linesAdded > 0 && (
<Text className="text-(--green-9) text-[13px]">+{linesAdded}</Text>
)}
{linesRemoved > 0 && (
<Text className="text-(--red-9) text-[13px]">-{linesRemoved}</Text>
)}
</Flex>
</Tooltip>
);
Comment on lines +16 to +57
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Logic duplicated between DiffStatsChip and DiffStatsBadge

Both components share the same useTaskDiffStats call, useReviewNavigationStore subscription pattern, handleClick toggle logic, GitDiff icon, and Tooltip with the same keyboard shortcut. The only real differences are the wrapping element (Flex vs Button), tooltip side (top vs bottom), and the review mode opened ("expanded" vs "split"). Consider extracting the shared state/callback logic into a shared hook (e.g. useDiffStatsToggle) to satisfy OnceAndOnlyOnce.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/sessions/components/DiffStatsChip.tsx
Line: 16-57

Comment:
**Logic duplicated between `DiffStatsChip` and `DiffStatsBadge`**

Both components share the same `useTaskDiffStats` call, `useReviewNavigationStore` subscription pattern, `handleClick` toggle logic, `GitDiff` icon, and Tooltip with the same keyboard shortcut. The only real differences are the wrapping element (`Flex` vs `Button`), tooltip side (`top` vs `bottom`), and the review mode opened (`"expanded"` vs `"split"`). Consider extracting the shared state/callback logic into a shared hook (e.g. `useDiffStatsToggle`) to satisfy OnceAndOnlyOnce.

How can I resolve this? If you propose a fix, please make it concise.

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,6 +21,7 @@ interface SessionFooterProps {
}

export function SessionFooter({
task,
isPromptPending,
promptStartedAt,
lastGenerationDuration,
Expand All @@ -28,6 +32,12 @@ export function SessionFooter({
isCompacting = false,
usage,
}: SessionFooterProps) {
const rightSide = (
<Flex align="center" gap="3">
{task && <DiffStatsChip task={task} />}
<ContextUsageIndicator usage={usage ?? null} />
</Flex>
);
if (isPromptPending && !isCompacting) {
if (hasPendingPermission) {
return (
Expand All @@ -42,7 +52,7 @@ export function SessionFooter({
<Pause size={14} weight="fill" />
<Text className="text-[13px]">Awaiting permission...</Text>
</Flex>
<ContextUsageIndicator usage={usage ?? null} />
{rightSide}
</Flex>
</Box>
);
Expand All @@ -62,7 +72,7 @@ export function SessionFooter({
</Text>
)}
</Flex>
<ContextUsageIndicator usage={usage ?? null} />
{rightSide}
</Flex>
</Box>
);
Expand Down Expand Up @@ -91,7 +101,7 @@ export function SessionFooter({
</Text>
</Flex>
)}
<ContextUsageIndicator usage={usage ?? null} />
{rightSide}
</Flex>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -97,6 +98,7 @@ function resolveAllowAlwaysUpgradeMode(
export function SessionView({
events,
taskId,
task,
isRunning,
isPromptPending = false,
promptStartedAt,
Expand Down Expand Up @@ -443,6 +445,7 @@ export function SessionView({
promptStartedAt={promptStartedAt}
repoPath={repoPath}
taskId={taskId}
task={task}
slackThreadUrl={slackThreadUrl}
/>
<Box className="border-gray-4 border-t">
Expand Down Expand Up @@ -513,6 +516,7 @@ export function SessionView({
promptStartedAt={promptStartedAt}
repoPath={repoPath}
taskId={taskId}
task={task}
slackThreadUrl={slackThreadUrl}
compact={compact}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export function TaskLogsPanel({ taskId, task, hideInput }: TaskLogsPanelProps) {
<SessionView
events={events}
taskId={taskId}
task={task}
isRunning={isRunning}
isSuspended={isSuspended}
onRestoreWorktree={
Expand Down
Loading