Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit fea999b

Browse files
committed
feat: improve retention UX with confirmation dialog and friendly messaging
- Add confirmation dialog before changing retention settings to prevent accidental changes - Move task count display from About tab to History View settings popover - Combine redundant description/warning into single, clear warning message - Update task count display to be more human-friendly ('X tasks in history') - Update all 18 locale files with consistent messaging - Revert About.tsx to original state (no retention UI there)
1 parent 739f6c1 commit fea999b

21 files changed

Lines changed: 409 additions & 282 deletions

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

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { memo, useState, useMemo } from "react"
2-
import { ArrowLeft, Settings } from "lucide-react"
1+
import React, { memo, useState, useMemo, useCallback, useEffect } from "react"
2+
import { ArrowLeft, Settings, FolderOpen, RefreshCw, Loader2 } from "lucide-react"
33
import { DeleteTaskDialog } from "./DeleteTaskDialog"
44
import { BatchDeleteTaskDialog } from "./BatchDeleteTaskDialog"
55
import { Virtuoso } from "react-virtuoso"
@@ -11,6 +11,14 @@ import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
1111
import { vscode } from "@/utils/vscode"
1212
import { useExtensionState } from "@/context/ExtensionStateContext"
1313
import {
14+
AlertDialog,
15+
AlertDialogAction,
16+
AlertDialogCancel,
17+
AlertDialogContent,
18+
AlertDialogDescription,
19+
AlertDialogFooter,
20+
AlertDialogHeader,
21+
AlertDialogTitle,
1422
Button,
1523
Checkbox,
1624
Popover,
@@ -48,7 +56,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
4856
showAllWorkspaces,
4957
setShowAllWorkspaces,
5058
} = useTaskSearch()
51-
const { taskHistoryRetention } = useExtensionState()
59+
const { taskHistoryRetention, taskHistorySize } = useExtensionState()
5260
const { t } = useAppTranslation()
5361

5462
// Use grouped tasks hook
@@ -60,6 +68,39 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
6068
const [selectedTaskIds, setSelectedTaskIds] = useState<string[]>([])
6169
const [showBatchDeleteDialog, setShowBatchDeleteDialog] = useState<boolean>(false)
6270
const [isRetentionPopoverOpen, setIsRetentionPopoverOpen] = useState(false)
71+
const [pendingRetention, setPendingRetention] = useState<TaskHistoryRetentionSetting | null>(null)
72+
const [showRetentionConfirmDialog, setShowRetentionConfirmDialog] = useState(false)
73+
const [isRefreshingTaskCount, setIsRefreshingTaskCount] = useState(false)
74+
const [cachedTaskCount, setCachedTaskCount] = useState<number | undefined>(taskHistorySize?.taskCount)
75+
76+
// Update cached task count when taskHistorySize changes
77+
useEffect(() => {
78+
if (taskHistorySize) {
79+
setCachedTaskCount(taskHistorySize.taskCount)
80+
setIsRefreshingTaskCount(false)
81+
}
82+
}, [taskHistorySize])
83+
84+
// Handle refresh task count
85+
const handleRefreshTaskCount = useCallback(() => {
86+
setIsRefreshingTaskCount(true)
87+
vscode.postMessage({ type: "refreshTaskHistorySize" })
88+
}, [])
89+
90+
// Get task count display text
91+
const getTaskCountDisplayText = (): string => {
92+
const count = taskHistorySize?.taskCount ?? cachedTaskCount
93+
if (count === undefined) {
94+
return t("settings:taskHistoryStorage.clickToCount")
95+
}
96+
if (count === 0) {
97+
return t("settings:taskHistoryStorage.empty")
98+
}
99+
if (count === 1) {
100+
return t("settings:taskHistoryStorage.countSingular")
101+
}
102+
return t("settings:taskHistoryStorage.count", { count })
103+
}
63104

64105
// Normalize retention setting to ensure it's valid
65106
const normalizedRetention: TaskHistoryRetentionSetting = TASK_HISTORY_RETENTION_OPTIONS.includes(
@@ -68,9 +109,31 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
68109
? (taskHistoryRetention as TaskHistoryRetentionSetting)
69110
: "never"
70111

71-
// Handle retention setting change
112+
// Handle retention setting change - show confirmation dialog first
72113
const handleRetentionChange = (value: TaskHistoryRetentionSetting) => {
73-
vscode.postMessage({ type: "updateSettings", updatedSettings: { taskHistoryRetention: value } })
114+
// If selecting the same value, do nothing
115+
if (value === normalizedRetention) {
116+
return
117+
}
118+
// Show confirmation dialog for any change
119+
setPendingRetention(value)
120+
setShowRetentionConfirmDialog(true)
121+
}
122+
123+
// Confirm retention change
124+
const confirmRetentionChange = () => {
125+
if (pendingRetention !== null) {
126+
vscode.postMessage({ type: "updateSettings", updatedSettings: { taskHistoryRetention: pendingRetention } })
127+
}
128+
setShowRetentionConfirmDialog(false)
129+
setPendingRetention(null)
130+
setIsRetentionPopoverOpen(false)
131+
}
132+
133+
// Cancel retention change
134+
const cancelRetentionChange = () => {
135+
setShowRetentionConfirmDialog(false)
136+
setPendingRetention(null)
74137
}
75138

76139
// Get subtask count for a task
@@ -148,7 +211,28 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
148211
</StandardTooltip>
149212
<PopoverContent className="w-72" align="end">
150213
<div className="space-y-3">
151-
<h4 className="font-medium text-sm">{t("settings:aboutRetention.label")}</h4>
214+
{/* Task count display */}
215+
<div className="flex items-center gap-2">
216+
<FolderOpen className="size-4 text-vscode-descriptionForeground shrink-0" />
217+
<span className="text-sm">{getTaskCountDisplayText()}</span>
218+
<Button
219+
variant="ghost"
220+
size="sm"
221+
onClick={handleRefreshTaskCount}
222+
disabled={isRefreshingTaskCount}
223+
className="h-6 w-6 p-0 ml-auto"
224+
title={t("settings:taskHistoryStorage.refresh")}>
225+
{isRefreshingTaskCount ? (
226+
<Loader2 className="size-3.5 animate-spin" />
227+
) : (
228+
<RefreshCw className="size-3.5" />
229+
)}
230+
</Button>
231+
</div>
232+
233+
<div className="border-t border-vscode-settings-headerBorder pt-3">
234+
<h4 className="font-medium text-sm">{t("settings:aboutRetention.label")}</h4>
235+
</div>
152236
<Select
153237
value={normalizedRetention}
154238
onValueChange={(value: TaskHistoryRetentionSetting) => {
@@ -175,9 +259,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
175259
</SelectContent>
176260
</Select>
177261
<p className="text-vscode-descriptionForeground text-xs">
178-
{t("settings:aboutRetention.description")}
262+
{t("settings:aboutRetention.warning")}
179263
</p>
180-
<p className="text-red-500 text-xs">{t("settings:aboutRetention.warning")}</p>
181264
</div>
182265
</PopoverContent>
183266
</Popover>
@@ -421,6 +504,32 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
421504
}}
422505
/>
423506
)}
507+
508+
{/* Retention change confirmation dialog */}
509+
<AlertDialog open={showRetentionConfirmDialog} onOpenChange={setShowRetentionConfirmDialog}>
510+
<AlertDialogContent>
511+
<AlertDialogHeader>
512+
<AlertDialogTitle>{t("settings:aboutRetention.confirmDialog.title")}</AlertDialogTitle>
513+
<AlertDialogDescription>
514+
{pendingRetention === "never"
515+
? t("settings:aboutRetention.confirmDialog.descriptionNever")
516+
: t("settings:aboutRetention.confirmDialog.description", {
517+
period: pendingRetention,
518+
})}
519+
</AlertDialogDescription>
520+
</AlertDialogHeader>
521+
<AlertDialogFooter>
522+
<AlertDialogCancel onClick={cancelRetentionChange}>
523+
{t("settings:aboutRetention.confirmDialog.cancel")}
524+
</AlertDialogCancel>
525+
<AlertDialogAction onClick={confirmRetentionChange}>
526+
{pendingRetention === "never"
527+
? t("settings:aboutRetention.confirmDialog.confirmNever")
528+
: t("settings:aboutRetention.confirmDialog.confirm")}
529+
</AlertDialogAction>
530+
</AlertDialogFooter>
531+
</AlertDialogContent>
532+
</AlertDialog>
424533
</Tab>
425534
)
426535
}

webview-ui/src/components/settings/About.tsx

Lines changed: 4 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
1-
import { HTMLAttributes, useState, useCallback, useEffect } from "react"
1+
import { HTMLAttributes } from "react"
22
import { useAppTranslation } from "@/i18n/TranslationContext"
33
import { Trans } from "react-i18next"
4-
import {
5-
Download,
6-
Upload,
7-
TriangleAlert,
8-
Bug,
9-
Lightbulb,
10-
Shield,
11-
MessageCircle,
12-
MessagesSquare,
13-
RefreshCw,
14-
FolderOpen,
15-
Loader2,
16-
} from "lucide-react"
4+
import { Download, Upload, TriangleAlert, Bug, Lightbulb, Shield, MessageCircle, MessagesSquare } from "lucide-react"
175
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
186

197
import type { TelemetrySetting } from "@roo-code/types"
@@ -28,63 +16,15 @@ import { SectionHeader } from "./SectionHeader"
2816
import { Section } from "./Section"
2917
import { SearchableSetting } from "./SearchableSetting"
3018

31-
type TaskHistorySize = {
32-
taskCount: number
33-
}
34-
3519
type AboutProps = HTMLAttributes<HTMLDivElement> & {
3620
telemetrySetting: TelemetrySetting
3721
setTelemetrySetting: (setting: TelemetrySetting) => void
3822
debug?: boolean
3923
setDebug?: (debug: boolean) => void
40-
taskHistorySize?: TaskHistorySize
4124
}
4225

43-
export const About = ({
44-
telemetrySetting,
45-
setTelemetrySetting,
46-
debug,
47-
setDebug,
48-
taskHistorySize,
49-
className,
50-
...props
51-
}: AboutProps) => {
26+
export const About = ({ telemetrySetting, setTelemetrySetting, debug, setDebug, className, ...props }: AboutProps) => {
5227
const { t } = useAppTranslation()
53-
const [isRefreshing, setIsRefreshing] = useState(false)
54-
const [cachedSize, setCachedSize] = useState<TaskHistorySize | undefined>(taskHistorySize)
55-
56-
// Update cached size when taskHistorySize changes and reset refreshing state
57-
useEffect(() => {
58-
if (taskHistorySize) {
59-
setCachedSize(taskHistorySize)
60-
setIsRefreshing(false)
61-
}
62-
}, [taskHistorySize])
63-
64-
// NOTE: No auto-trigger on mount - user must click refresh button
65-
// This is intentional for performance with large task counts (e.g., 9000+ tasks)
66-
67-
const handleRefreshTaskCount = useCallback(() => {
68-
setIsRefreshing(true)
69-
vscode.postMessage({ type: "refreshTaskHistorySize" })
70-
}, [])
71-
72-
const getTaskCountDisplayText = (): string => {
73-
// Use cached size if available, otherwise prompt user to click refresh
74-
const displaySize = taskHistorySize || cachedSize
75-
if (!displaySize) {
76-
return t("settings:taskHistoryStorage.clickToCount")
77-
}
78-
if (displaySize.taskCount === 0) {
79-
return t("settings:taskHistoryStorage.empty")
80-
}
81-
if (displaySize.taskCount === 1) {
82-
return t("settings:taskHistoryStorage.countSingular")
83-
}
84-
return t("settings:taskHistoryStorage.count", {
85-
count: displaySize.taskCount,
86-
})
87-
}
8828

8929
return (
9030
<div className={cn("flex flex-col gap-2", className)} {...props}>
@@ -191,37 +131,10 @@ export const About = ({
191131
</Section>
192132

193133
<Section className="space-y-0">
194-
<SearchableSetting
195-
settingId="about-task-history-count"
196-
section="about"
197-
label={t("settings:taskHistoryStorage.label")}
198-
className="mt-4">
199-
<div className="flex items-center gap-2">
200-
<FolderOpen className="size-4 text-vscode-descriptionForeground shrink-0" />
201-
<span className="text-sm">
202-
{t("settings:taskHistoryStorage.label")}: {getTaskCountDisplayText()}
203-
</span>
204-
<Button
205-
variant="ghost"
206-
size="sm"
207-
onClick={handleRefreshTaskCount}
208-
disabled={isRefreshing}
209-
className="h-6 w-6 p-0"
210-
title={t("settings:taskHistoryStorage.refresh")}>
211-
{isRefreshing ? (
212-
<Loader2 className="size-3.5 animate-spin" />
213-
) : (
214-
<RefreshCw className="size-3.5" />
215-
)}
216-
</Button>
217-
</div>
218-
</SearchableSetting>
219-
220134
<SearchableSetting
221135
settingId="about-manage-settings"
222136
section="about"
223-
label={t("settings:about.manageSettings")}
224-
className="mt-4 pt-4 border-t border-vscode-settings-headerBorder">
137+
label={t("settings:about.manageSettings")}>
225138
<h3>{t("settings:about.manageSettings")}</h3>
226139
<div className="flex flex-wrap items-center gap-2">
227140
<Button onClick={() => vscode.postMessage({ type: "exportSettings" })} className="w-28">

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
218218
taskHistoryRetention,
219219
} = cachedState
220220

221-
// taskHistorySize is read-only (not a saveable setting) so we use extensionState directly
222-
const taskHistorySize = extensionState.taskHistorySize
223-
224221
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
225222

226223
useEffect(() => {
@@ -974,7 +971,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
974971
setTelemetrySetting={setTelemetrySetting}
975972
debug={cachedState.debug}
976973
setDebug={setDebug}
977-
taskHistorySize={taskHistorySize}
978974
/>
979975
)}
980976
</SearchIndexProvider>

webview-ui/src/i18n/locales/ca/settings.json

Lines changed: 16 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)