Skip to content

Commit 24584f5

Browse files
roomote[bot]roomote-agentBruno Bergherbrunoberghermrubens
authored
feat: clean up task list in HistoryPreview and History components (#6687)
Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Bruno Bergher <me@brunobergher.comexport> Co-authored-by: Bruno Bergher <me@brunobergher.com> Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
1 parent 477b85d commit 24584f5

30 files changed

+558
-211
lines changed

webview-ui/src/components/common/VersionIndicator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const VersionIndicator: React.FC<VersionIndicatorProps> = ({ onClick, className
1313
return (
1414
<button
1515
onClick={onClick}
16-
className={`text-xs text-vscode-descriptionForeground hover:text-vscode-foreground transition-colors cursor-pointer px-2 py-1 rounded border border-vscode-panel-border hover:border-vscode-focusBorder ${className}`}
16+
className={`text-xs text-vscode-descriptionForeground hover:text-vscode-foreground transition-colors cursor-pointer px-2 py-1 rounded border ${className}`}
1717
aria-label={t("chat:versionIndicator.ariaLabel", { version: Package.version })}>
1818
v{Package.version}
1919
</button>

webview-ui/src/components/history/DeleteButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const DeleteButton = ({ itemId, onDelete }: DeleteButtonProps) => {
3131
size="icon"
3232
data-testid="delete-task-button"
3333
onClick={handleDeleteClick}
34-
className="group-hover:opacity-100 opacity-50 transition-opacity">
34+
className="opacity-70">
3535
<span className="codicon codicon-trash size-4 align-middle text-vscode-descriptionForeground" />
3636
</Button>
3737
</StandardTooltip>

webview-ui/src/components/history/HistoryView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
222222
</div>
223223
</TabHeader>
224224

225-
<TabContent className="p-0">
225+
<TabContent className="px-2 py-0">
226226
<Virtuoso
227227
className="flex-1 overflow-y-scroll"
228228
data={tasks}
@@ -243,15 +243,15 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
243243
isSelected={selectedTaskIds.includes(item.id)}
244244
onToggleSelection={toggleTaskSelection}
245245
onDelete={setDeleteTaskId}
246-
className="m-2 mr-0"
246+
className="m-2"
247247
/>
248248
)}
249249
/>
250250
</TabContent>
251251

252252
{/* Fixed action bar at bottom - only shown in selection mode with selected items */}
253253
{isSelectionMode && selectedTaskIds.length > 0 && (
254-
<div className="fixed bottom-0 left-0 right-0 bg-vscode-editor-background border-t border-vscode-panel-border p-2 flex justify-between items-center">
254+
<div className="fixed bottom-0 left-0 right-2 bg-vscode-editor-background border-t border-vscode-panel-border p-2 flex justify-between items-center">
255255
<div className="text-vscode-foreground">
256256
{t("history:selectedItems", { selected: selectedTaskIds.length, total: tasks.length })}
257257
</div>

webview-ui/src/components/history/TaskItem.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { vscode } from "@/utils/vscode"
55
import { cn } from "@/lib/utils"
66
import { Checkbox } from "@/components/ui/checkbox"
77

8-
import TaskItemHeader from "./TaskItemHeader"
98
import TaskItemFooter from "./TaskItemFooter"
109

1110
interface DisplayHistoryItem extends HistoryItem {
@@ -48,11 +47,11 @@ const TaskItem = ({
4847
key={item.id}
4948
data-testid={`task-item-${item.id}`}
5049
className={cn(
51-
"cursor-pointer group bg-vscode-editor-background rounded relative overflow-hidden hover:border-vscode-toolbar-hoverBackground/60",
50+
"cursor-pointer group bg-vscode-editor-background rounded relative overflow-hidden border border-transparent hover:bg-vscode-list-hoverBackground transition-colors",
5251
className,
5352
)}
5453
onClick={handleClick}>
55-
<div className="flex gap-2 p-3">
54+
<div className={(!isCompact && isSelectionMode ? "pl-3 pb-3" : "pl-4") + " flex gap-3 px-3 pt-3 pb-1"}>
5655
{/* Selection checkbox - only in full variant */}
5756
{!isCompact && isSelectionMode && (
5857
<div
@@ -69,24 +68,25 @@ const TaskItem = ({
6968
)}
7069

7170
<div className="flex-1 min-w-0">
72-
{/* Header with metadata */}
73-
<TaskItemHeader item={item} isSelectionMode={isSelectionMode} onDelete={onDelete} />
74-
75-
{/* Task content */}
7671
<div
77-
className={cn("overflow-hidden whitespace-pre-wrap text-vscode-foreground text-ellipsis", {
78-
"text-base line-clamp-3": !isCompact,
79-
"line-clamp-2": isCompact,
80-
})}
72+
className={cn(
73+
"overflow-hidden whitespace-pre-wrap text-vscode-foreground text-ellipsis line-clamp-2",
74+
{
75+
"text-base": !isCompact,
76+
},
77+
!isCompact && isSelectionMode ? "mb-1" : "",
78+
)}
8179
data-testid="task-content"
8280
{...(item.highlight ? { dangerouslySetInnerHTML: { __html: item.highlight } } : {})}>
8381
{item.highlight ? undefined : item.task}
8482
</div>
83+
<TaskItemFooter
84+
item={item}
85+
variant={variant}
86+
isSelectionMode={isSelectionMode}
87+
onDelete={onDelete}
88+
/>
8589

86-
{/* Task Item Footer */}
87-
<TaskItemFooter item={item} variant={variant} isSelectionMode={isSelectionMode} />
88-
89-
{/* Workspace info */}
9090
{showWorkspace && item.workspace && (
9191
<div className="flex flex-row gap-1 text-vscode-descriptionForeground text-xs mt-1">
9292
<span className="codicon codicon-folder scale-80" />

webview-ui/src/components/history/TaskItemFooter.tsx

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,41 @@
11
import React from "react"
22
import type { HistoryItem } from "@roo-code/types"
3-
import { Coins, FileIcon } from "lucide-react"
4-
import prettyBytes from "pretty-bytes"
5-
import { formatLargeNumber } from "@/utils/format"
3+
import { formatTimeAgo } from "@/utils/format"
64
import { CopyButton } from "./CopyButton"
75
import { ExportButton } from "./ExportButton"
6+
import { DeleteButton } from "./DeleteButton"
7+
import { StandardTooltip } from "../ui/standard-tooltip"
88

99
export interface TaskItemFooterProps {
1010
item: HistoryItem
1111
variant: "compact" | "full"
1212
isSelectionMode?: boolean
13+
onDelete?: (taskId: string) => void
1314
}
1415

15-
const TaskItemFooter: React.FC<TaskItemFooterProps> = ({ item, variant, isSelectionMode = false }) => {
16+
const TaskItemFooter: React.FC<TaskItemFooterProps> = ({ item, variant, isSelectionMode = false, onDelete }) => {
1617
return (
17-
<div className="text-xs text-vscode-descriptionForeground flex justify-between items-center mt-1">
18-
<div className="flex gap-2">
19-
{!!(item.cacheReads || item.cacheWrites) && (
20-
<span className="flex items-center" data-testid="cache-compact">
21-
<i className="mr-1 codicon codicon-cloud-upload text-sm! text-vscode-descriptionForeground" />
22-
<span className="inline-block mr-1">{formatLargeNumber(item.cacheWrites || 0)}</span>
23-
<i className="mr-1 codicon codicon-cloud-download text-sm! text-vscode-descriptionForeground" />
24-
<span>{formatLargeNumber(item.cacheReads || 0)}</span>
25-
</span>
26-
)}
27-
28-
{/* Full Tokens */}
29-
{!!(item.tokensIn || item.tokensOut) && (
30-
<span className="flex items-center gap-1">
31-
<span data-testid="tokens-in-footer-compact">{formatLargeNumber(item.tokensIn || 0)}</span>
32-
<span data-testid="tokens-out-footer-compact">{formatLargeNumber(item.tokensOut || 0)}</span>
33-
</span>
34-
)}
35-
36-
{/* Full Cost */}
18+
<div className="text-xs text-vscode-descriptionForeground flex justify-between items-center">
19+
<div className="flex gap-2 items-center text-vscode-descriptionForeground/60">
20+
{/* Datetime with time-ago format */}
21+
<StandardTooltip content={new Date(item.ts).toLocaleString()}>
22+
<span className="first-letter:uppercase">{formatTimeAgo(item.ts)}</span>
23+
</StandardTooltip>
24+
<span>·</span>
25+
{/* Cost */}
3726
{!!item.totalCost && (
38-
<span className="flex items-center">
39-
<Coins className="inline-block size-[1em] mr-1" />
40-
<span data-testid="cost-footer-compact">{"$" + item.totalCost.toFixed(2)}</span>
41-
</span>
42-
)}
43-
44-
{!!item.size && (
45-
<span className="flex items-center">
46-
<FileIcon className="inline-block size-[1em] mr-1" />
47-
<span data-testid="size-footer-compact">{prettyBytes(item.size)}</span>
27+
<span className="flex items-center" data-testid="cost-footer-compact">
28+
{"$" + item.totalCost.toFixed(2)}
4829
</span>
4930
)}
5031
</div>
5132

5233
{/* Action Buttons for non-compact view */}
5334
{!isSelectionMode && (
54-
<div className="flex flex-row gap-0 items-center opacity-50 hover:opacity-100">
35+
<div className="flex flex-row gap-0 items-center text-vscode-descriptionForeground/60 hover:text-vscode-descriptionForeground">
5536
<CopyButton itemTask={item.task} />
5637
{variant === "full" && <ExportButton itemId={item.id} />}
38+
{onDelete && <DeleteButton itemId={item.id} onDelete={onDelete} />}
5739
</div>
5840
)}
5941
</div>

webview-ui/src/components/history/TaskItemHeader.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ describe("HistoryPreview", () => {
148148
expect(screen.getByTestId("task-item-task-2")).toBeInTheDocument()
149149
expect(screen.getByTestId("task-item-task-3")).toBeInTheDocument()
150150
expect(screen.queryByTestId("task-item-task-4")).not.toBeInTheDocument()
151+
expect(screen.queryByTestId("task-item-task-5")).not.toBeInTheDocument()
152+
expect(screen.queryByTestId("task-item-task-6")).not.toBeInTheDocument()
151153
})
152154

153155
it("renders only 1 task when there is only 1 task", () => {

webview-ui/src/components/history/__tests__/TaskItem.spec.tsx

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ vi.mock("@src/i18n/TranslationContext", () => ({
99
}),
1010
}))
1111

12+
vi.mock("@/utils/format", () => ({
13+
formatTimeAgo: vi.fn(() => "2 hours ago"),
14+
formatDate: vi.fn(() => "January 15 at 2:30 PM"),
15+
formatLargeNumber: vi.fn((num: number) => num.toString()),
16+
}))
17+
1218
const mockTask = {
1319
id: "1",
1420
number: 1,
@@ -74,47 +80,33 @@ describe("TaskItem", () => {
7480
expect(screen.getByTestId("export")).toBeInTheDocument()
7581
})
7682

77-
it("displays cache information when present", () => {
78-
const mockTaskWithCache = {
79-
...mockTask,
80-
cacheReads: 10,
81-
cacheWrites: 5,
82-
}
83-
83+
it("displays time ago information", () => {
8484
render(
8585
<TaskItem
86-
item={mockTaskWithCache}
86+
item={mockTask}
8787
variant="full"
8888
isSelected={false}
8989
onToggleSelection={vi.fn()}
9090
isSelectionMode={false}
9191
/>,
9292
)
9393

94-
// Should display cache information in the footer
95-
expect(screen.getByTestId("cache-compact")).toBeInTheDocument()
96-
expect(screen.getByText("5")).toBeInTheDocument() // cache writes
97-
expect(screen.getByText("10")).toBeInTheDocument() // cache reads
94+
// Should display time ago format
95+
expect(screen.getByText(/ago/)).toBeInTheDocument()
9896
})
9997

100-
it("does not display cache information when not present", () => {
101-
const mockTaskWithoutCache = {
102-
...mockTask,
103-
cacheReads: 0,
104-
cacheWrites: 0,
105-
}
106-
98+
it("applies hover effect class", () => {
10799
render(
108100
<TaskItem
109-
item={mockTaskWithoutCache}
101+
item={mockTask}
110102
variant="full"
111103
isSelected={false}
112104
onToggleSelection={vi.fn()}
113105
isSelectionMode={false}
114106
/>,
115107
)
116108

117-
// Cache section should not be present
118-
expect(screen.queryByTestId("cache-compact")).not.toBeInTheDocument()
109+
const taskItem = screen.getByTestId("task-item-1")
110+
expect(taskItem).toHaveClass("hover:bg-vscode-list-hoverBackground")
119111
})
120112
})

webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ vi.mock("@src/i18n/TranslationContext", () => ({
88
}),
99
}))
1010

11+
vi.mock("@/utils/format", () => ({
12+
formatTimeAgo: vi.fn(() => "2 hours ago"),
13+
formatDate: vi.fn(() => "January 15 at 2:30 PM"),
14+
formatLargeNumber: vi.fn((num: number) => num.toString()),
15+
}))
16+
1117
const mockItem = {
1218
id: "1",
1319
number: 1,
@@ -20,12 +26,11 @@ const mockItem = {
2026
}
2127

2228
describe("TaskItemFooter", () => {
23-
it("renders token information", () => {
29+
it("renders time ago information", () => {
2430
render(<TaskItemFooter item={mockItem} variant="full" />)
2531

26-
// Check for token counts using testids since the text is split across elements
27-
expect(screen.getByTestId("tokens-in-footer-compact")).toBeInTheDocument()
28-
expect(screen.getByTestId("tokens-out-footer-compact")).toBeInTheDocument()
32+
// Should show time ago format
33+
expect(screen.getByText(/ago/)).toBeInTheDocument()
2934
})
3035

3136
it("renders cost information", () => {
@@ -43,31 +48,38 @@ describe("TaskItemFooter", () => {
4348
expect(screen.getByTestId("export")).toBeInTheDocument()
4449
})
4550

46-
it("renders cache information when present", () => {
47-
const mockItemWithCache = {
48-
...mockItem,
49-
cacheReads: 5,
50-
cacheWrites: 3,
51-
}
51+
it("hides export button in compact variant", () => {
52+
render(<TaskItemFooter item={mockItem} variant="compact" />)
53+
54+
// Should show copy button but not export button
55+
expect(screen.getByTestId("copy-prompt-button")).toBeInTheDocument()
56+
expect(screen.queryByTestId("export")).not.toBeInTheDocument()
57+
})
58+
59+
it("hides action buttons in selection mode", () => {
60+
render(<TaskItemFooter item={mockItem} variant="full" isSelectionMode={true} />)
61+
62+
// Should not show any action buttons
63+
expect(screen.queryByTestId("copy-prompt-button")).not.toBeInTheDocument()
64+
expect(screen.queryByTestId("export")).not.toBeInTheDocument()
65+
expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument()
66+
})
5267

53-
render(<TaskItemFooter item={mockItemWithCache} variant="full" />)
68+
it("shows delete button when not in selection mode and onDelete is provided", () => {
69+
render(<TaskItemFooter item={mockItem} variant="full" isSelectionMode={false} onDelete={vi.fn()} />)
5470

55-
// Check for cache display using testid
56-
expect(screen.getByTestId("cache-compact")).toBeInTheDocument()
57-
expect(screen.getByText("3")).toBeInTheDocument() // cache writes
58-
expect(screen.getByText("5")).toBeInTheDocument() // cache reads
71+
expect(screen.getByTestId("delete-task-button")).toBeInTheDocument()
5972
})
6073

61-
it("does not render cache information when not present", () => {
62-
const mockItemWithoutCache = {
63-
...mockItem,
64-
cacheReads: 0,
65-
cacheWrites: 0,
66-
}
74+
it("does not show delete button in selection mode", () => {
75+
render(<TaskItemFooter item={mockItem} variant="full" isSelectionMode={true} onDelete={vi.fn()} />)
76+
77+
expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument()
78+
})
6779

68-
render(<TaskItemFooter item={mockItemWithoutCache} variant="full" />)
80+
it("does not show delete button when onDelete is not provided", () => {
81+
render(<TaskItemFooter item={mockItem} variant="full" isSelectionMode={false} />)
6982

70-
// Cache section should not be present
71-
expect(screen.queryByTestId("cache-compact")).not.toBeInTheDocument()
83+
expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument()
7284
})
7385
})

0 commit comments

Comments
 (0)