feat: add "View Diff" button to task actions for viewing all changes since task started

This commit is contained in:
Roo Code 2026-04-18 20:37:23 +00:00
parent cb83656718
commit e686deea35
3 changed files with 107 additions and 3 deletions

View file

@ -1,4 +1,4 @@
import { useState } from "react"
import { useState, useMemo, useCallback } from "react"
import { useTranslation } from "react-i18next"
import type { HistoryItem } from "@roo-code/types"
@ -9,7 +9,15 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
import { ShareButton } from "./ShareButton"
import { CopyIcon, CheckIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
import {
CopyIcon,
CheckIcon,
DownloadIcon,
Trash2Icon,
FileJsonIcon,
MessageSquareCodeIcon,
DiffIcon,
} from "lucide-react"
import { LucideIconButton } from "./LucideIconButton"
interface TaskActionsProps {
@ -21,7 +29,26 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
const { t } = useTranslation()
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
const { debug } = useExtensionState()
const { debug, enableCheckpoints, clineMessages } = useExtensionState()
const firstCheckpointHash = useMemo(() => {
const msg = (clineMessages ?? []).find((m) => m.say === "checkpoint_saved")
return msg?.text
}, [clineMessages])
const lastCheckpointHash = useMemo(() => {
const checkpointMessages = (clineMessages ?? []).filter((m) => m.say === "checkpoint_saved")
return checkpointMessages.length > 0 ? checkpointMessages[checkpointMessages.length - 1].text : undefined
}, [clineMessages])
const onViewFullDiff = useCallback(() => {
if (lastCheckpointHash) {
vscode.postMessage({
type: "checkpointDiff",
payload: { commitHash: lastCheckpointHash, mode: "full" },
})
}
}, [lastCheckpointHash])
return (
<div className="flex flex-row items-center -ml-0.5 mt-1 gap-1">
@ -31,6 +58,10 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
onClick={() => vscode.postMessage({ type: "exportCurrentTask" })}
/>
{enableCheckpoints && firstCheckpointHash && (
<LucideIconButton icon={DiffIcon} title={t("chat:task.viewDiff")} onClick={onViewFullDiff} />
)}
{item?.task && (
<LucideIconButton
icon={showCopyFeedback ? CheckIcon : CopyIcon}

View file

@ -51,6 +51,7 @@ vi.mock("react-i18next", () => ({
"chat:task.sharingDisabledByOrganization": "Sharing disabled by organization",
"chat:task.openApiHistory": "Open API History",
"chat:task.openUiHistory": "Open UI History",
"chat:task.viewDiff": "View all changes since task started",
"cloud:cloudBenefitsTitle": "Connect to Roo Code Cloud",
"cloud:cloudBenefitHistory": "Access your task history from anywhere",
"cloud:cloudBenefitSharing": "Share tasks with your team",
@ -525,4 +526,75 @@ describe("TaskActions", () => {
})
})
})
describe("View Diff Button", () => {
it("renders view diff button when checkpoints are enabled and checkpoint exists", () => {
mockUseExtensionState.mockReturnValue({
sharingEnabled: true,
cloudIsAuthenticated: true,
cloudUserInfo: { organizationName: "Test Organization" },
enableCheckpoints: true,
clineMessages: [{ say: "checkpoint_saved", text: "abc123", ts: 1000 }],
} as any)
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
const viewDiffButton = screen.getByLabelText("View all changes since task started")
expect(viewDiffButton).toBeInTheDocument()
})
it("does not render view diff button when checkpoints are disabled", () => {
mockUseExtensionState.mockReturnValue({
sharingEnabled: true,
cloudIsAuthenticated: true,
cloudUserInfo: { organizationName: "Test Organization" },
enableCheckpoints: false,
clineMessages: [{ say: "checkpoint_saved", text: "abc123", ts: 1000 }],
} as any)
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
const viewDiffButton = screen.queryByLabelText("View all changes since task started")
expect(viewDiffButton).toBeNull()
})
it("does not render view diff button when no checkpoints exist", () => {
mockUseExtensionState.mockReturnValue({
sharingEnabled: true,
cloudIsAuthenticated: true,
cloudUserInfo: { organizationName: "Test Organization" },
enableCheckpoints: true,
clineMessages: [],
} as any)
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
const viewDiffButton = screen.queryByLabelText("View all changes since task started")
expect(viewDiffButton).toBeNull()
})
it("sends checkpointDiff message with full mode when view diff button is clicked", () => {
mockUseExtensionState.mockReturnValue({
sharingEnabled: true,
cloudIsAuthenticated: true,
cloudUserInfo: { organizationName: "Test Organization" },
enableCheckpoints: true,
clineMessages: [
{ say: "checkpoint_saved", text: "first-hash", ts: 1000 },
{ say: "text", text: "some message", ts: 2000 },
{ say: "checkpoint_saved", text: "last-hash", ts: 3000 },
],
} as any)
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
const viewDiffButton = screen.getByLabelText("View all changes since task started")
fireEvent.click(viewDiffButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "checkpointDiff",
payload: { commitHash: "last-hash", mode: "full" },
})
})
})
})

View file

@ -27,6 +27,7 @@
"shareSuccessPublic": "Public link copied to clipboard",
"openApiHistory": "Open API History",
"openUiHistory": "Open UI History",
"viewDiff": "View all changes since task started",
"backToParentTask": "Parent task"
},
"unpin": "Unpin",