Skip to content

Commit 0c4905c

Browse files
committed
Brings back an 'enabled' toggle for auto-approve
1 parent 90091c6 commit 0c4905c

File tree

22 files changed

+104
-66
lines changed

22 files changed

+104
-66
lines changed

webview-ui/src/components/chat/AutoApproveDropdown.tsx

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React from "react"
2-
import { ListChecks, LayoutList, Settings, CheckCheck } from "lucide-react"
2+
import { ListChecks, LayoutList, Settings, CheckCheck, X } from "lucide-react"
33

44
import { vscode } from "@/utils/vscode"
55
import { cn } from "@/lib/utils"
66
import { useExtensionState } from "@/context/ExtensionStateContext"
77
import { useAppTranslation } from "@/i18n/TranslationContext"
88
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
9-
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
9+
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, ToggleSwitch } from "@/components/ui"
1010
import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
1111
import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles"
12+
import { useAutoApprovalState } from "@/hooks/useAutoApprovalState"
1213

1314
interface AutoApproveDropdownProps {
1415
disabled?: boolean
@@ -124,20 +125,24 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
124125
Object.keys(autoApproveSettingsConfig).forEach((key) => {
125126
onAutoApproveToggle(key as AutoApproveSetting, false)
126127
})
127-
// Disable master auto-approval
128-
if (autoApprovalEnabled) {
129-
setAutoApprovalEnabled(false)
130-
vscode.postMessage({ type: "autoApprovalEnabled", bool: false })
131-
}
132-
}, [onAutoApproveToggle, autoApprovalEnabled, setAutoApprovalEnabled])
128+
}, [onAutoApproveToggle])
133129

134130
const handleOpenSettings = React.useCallback(
135131
() =>
136132
window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }),
137133
[],
138134
)
139135

136+
// Handle the main auto-approval toggle
137+
const handleAutoApprovalToggle = React.useCallback(() => {
138+
const newValue = !(autoApprovalEnabled ?? false)
139+
setAutoApprovalEnabled(newValue)
140+
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
141+
}, [autoApprovalEnabled, setAutoApprovalEnabled])
142+
140143
// Calculate enabled and total counts as separate properties
144+
const settingsArray = Object.values(autoApproveSettingsConfig)
145+
141146
const enabledCount = React.useMemo(() => {
142147
return Object.values(toggles).filter((value) => !!value).length
143148
}, [toggles])
@@ -146,8 +151,7 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
146151
return Object.keys(toggles).length
147152
}, [toggles])
148153

149-
// Split settings into two columns
150-
const settingsArray = Object.values(autoApproveSettingsConfig)
154+
const { effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled)
151155

152156
return (
153157
<Popover open={open} onOpenChange={setOpen} data-testid="auto-approve-dropdown-root">
@@ -164,11 +168,18 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
164168
: "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer",
165169
triggerClassName,
166170
)}>
167-
<CheckCheck className="size-3 flex-shrink-0" />
171+
{!effectiveAutoApprovalEnabled ? (
172+
<X className="size-3 flex-shrink-0" />
173+
) : (
174+
<CheckCheck className="size-3 flex-shrink-0" />
175+
)}
176+
168177
<span className="truncate min-w-0">
169-
{enabledCount === totalCount
170-
? t("chat:autoApprove.triggerLabelAll")
171-
: t("chat:autoApprove.triggerLabel", { count: enabledCount })}
178+
{!effectiveAutoApprovalEnabled
179+
? t("chat:autoApprove.triggerLabelOff")
180+
: enabledCount === totalCount
181+
? t("chat:autoApprove.triggerLabelAll")
182+
: t("chat:autoApprove.triggerLabel", { count: enabledCount })}
172183
</span>
173184
</PopoverTrigger>
174185
</StandardTooltip>
@@ -206,10 +217,13 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
206217
"transition-all duration-150",
207218
"opacity-100 hover:opacity-70",
208219
"cursor-pointer",
220+
!effectiveAutoApprovalEnabled &&
221+
"opacity-50 cursor-not-allowed hover:opacity-50",
209222
isEnabled
210223
? "bg-vscode-button-background text-vscode-button-foreground"
211224
: "bg-vscode-button-background/15 text-vscode-foreground hover:bg-vscode-list-hoverBackground",
212225
)}
226+
disabled={!effectiveAutoApprovalEnabled}
213227
data-testid={`auto-approve-${key}`}>
214228
<span className={`codicon codicon-${icon} text-sm flex-shrink-0`} />
215229
<span className="flex-1 truncate">{t(labelKey)}</span>
@@ -225,6 +239,7 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
225239
<button
226240
aria-label={t("chat:autoApprove.selectAll")}
227241
onClick={handleSelectAll}
242+
disabled={!effectiveAutoApprovalEnabled}
228243
className={cn(
229244
"relative inline-flex items-center justify-center gap-1",
230245
"bg-transparent border-none px-2 py-1",
@@ -235,13 +250,15 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
235250
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
236251
"active:bg-[rgba(255,255,255,0.1)]",
237252
"cursor-pointer",
253+
!effectiveAutoApprovalEnabled && "opacity-50 hover:opacity-50 cursor-not-allowed",
238254
)}>
239255
<ListChecks className="w-3.5 h-3.5" />
240256
<span>{t("chat:autoApprove.all")}</span>
241257
</button>
242258
<button
243259
aria-label={t("chat:autoApprove.selectNone")}
244260
onClick={handleSelectNone}
261+
disabled={!effectiveAutoApprovalEnabled}
245262
className={cn(
246263
"relative inline-flex items-center justify-center gap-1",
247264
"bg-transparent border-none px-2 py-1",
@@ -252,11 +269,30 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
252269
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
253270
"active:bg-[rgba(255,255,255,0.1)]",
254271
"cursor-pointer",
272+
!effectiveAutoApprovalEnabled && "opacity-50 hover:opacity-50 cursor-not-allowed",
255273
)}>
256274
<LayoutList className="w-3.5 h-3.5" />
257275
<span>{t("chat:autoApprove.none")}</span>
258276
</button>
259277
</div>
278+
279+
<label
280+
className="flex items-center gap-2 pr-2 cursor-pointer"
281+
onClick={(e) => {
282+
// Prevent label click when clicking on the toggle switch itself
283+
if ((e.target as HTMLElement).closest('[role="switch"]')) {
284+
e.preventDefault()
285+
return
286+
}
287+
handleAutoApprovalToggle()
288+
}}>
289+
<ToggleSwitch
290+
checked={effectiveAutoApprovalEnabled}
291+
aria-label="Toggle auto-approval"
292+
onChange={handleAutoApprovalToggle}
293+
/>
294+
<span className={cn("text-sm font-bold select-none")}>Enabled</span>
295+
</label>
260296
</div>
261297
</div>
262298
</PopoverContent>

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

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { X, CheckCheck } from "lucide-react"
44
import { useAppTranslation } from "@/i18n/TranslationContext"
55
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
66
import { vscode } from "@/utils/vscode"
7-
import { Button, Input, Slider, StandardTooltip } from "@/components/ui"
7+
import { Button, Input, Slider } from "@/components/ui"
88

99
import { SetCachedStateField } from "./types"
1010
import { SectionHeader } from "./SectionHeader"
@@ -88,7 +88,7 @@ export const AutoApproveSettings = ({
8888

8989
const toggles = useAutoApprovalToggles()
9090

91-
const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled)
91+
const { effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled)
9292

9393
const handleAddCommand = () => {
9494
const currentCommands = allowedCommands ?? []
@@ -124,32 +124,16 @@ export const AutoApproveSettings = ({
124124
<Section>
125125
<div className="space-y-4">
126126
<div>
127-
{!hasEnabledOptions ? (
128-
<StandardTooltip content={t("settings:autoApprove.selectOptionsFirst")}>
129-
<VSCodeCheckbox
130-
checked={effectiveAutoApprovalEnabled}
131-
disabled={!hasEnabledOptions}
132-
aria-label={t("settings:autoApprove.disabledAriaLabel")}
133-
onChange={() => {
134-
// Do nothing when no options are enabled
135-
return
136-
}}>
137-
<span className="font-medium">{t("settings:autoApprove.enabled")}</span>
138-
</VSCodeCheckbox>
139-
</StandardTooltip>
140-
) : (
141-
<VSCodeCheckbox
142-
checked={effectiveAutoApprovalEnabled}
143-
disabled={!hasEnabledOptions}
144-
aria-label={t("settings:autoApprove.toggleAriaLabel")}
145-
onChange={() => {
146-
const newValue = !(autoApprovalEnabled ?? false)
147-
setAutoApprovalEnabled(newValue)
148-
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
149-
}}>
150-
<span className="font-medium">{t("settings:autoApprove.enabled")}</span>
151-
</VSCodeCheckbox>
152-
)}
127+
<VSCodeCheckbox
128+
checked={effectiveAutoApprovalEnabled}
129+
aria-label={t("settings:autoApprove.toggleAriaLabel")}
130+
onChange={() => {
131+
const newValue = !(autoApprovalEnabled ?? false)
132+
setAutoApprovalEnabled(newValue)
133+
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
134+
}}>
135+
<span className="font-medium">{t("settings:autoApprove.enabled")}</span>
136+
</VSCodeCheckbox>
153137
<div className="text-vscode-descriptionForeground text-sm mt-1">
154138
{t("settings:autoApprove.description")}
155139
</div>

webview-ui/src/hooks/__tests__/useAutoApprovalState.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ describe("useAutoApprovalState", () => {
124124
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
125125
})
126126

127-
it("should return false when autoApprovalEnabled is true but no toggles are enabled", () => {
127+
it("should return true when autoApprovalEnabled is true but no toggles are enabled", () => {
128128
const toggles = {
129129
alwaysAllowReadOnly: false,
130130
alwaysAllowWrite: false,
@@ -140,7 +140,7 @@ describe("useAutoApprovalState", () => {
140140

141141
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
142142

143-
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
143+
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
144144
})
145145

146146
it("should return true when autoApprovalEnabled is true and at least one toggle is enabled", () => {
@@ -217,7 +217,7 @@ describe("useAutoApprovalState", () => {
217217
rerender({ toggles: newToggles, autoApprovalEnabled: true })
218218

219219
expect(result.current.hasEnabledOptions).toBe(false)
220-
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
220+
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
221221
})
222222

223223
it("should recompute effectiveAutoApprovalEnabled when autoApprovalEnabled changes", () => {
@@ -263,7 +263,7 @@ describe("useAutoApprovalState", () => {
263263
const { result } = renderHook(() => useAutoApprovalState(toggles, true))
264264

265265
expect(result.current.hasEnabledOptions).toBe(false)
266-
expect(result.current.effectiveAutoApprovalEnabled).toBe(false)
266+
expect(result.current.effectiveAutoApprovalEnabled).toBe(true)
267267
})
268268

269269
it("should handle mixed truthy/falsy values correctly", () => {

webview-ui/src/hooks/useAutoApprovalState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ export function useAutoApprovalState(toggles: AutoApprovalToggles, autoApprovalE
1919
}, [toggles])
2020

2121
const effectiveAutoApprovalEnabled = useMemo(() => {
22-
return hasEnabledOptions && (autoApprovalEnabled ?? false)
23-
}, [hasEnabledOptions, autoApprovalEnabled])
22+
return autoApprovalEnabled ?? false
23+
}, [autoApprovalEnabled])
2424

2525
return {
2626
hasEnabledOptions,

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

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/de/chat.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@
279279
"selectOptionsFirst": "Select at least one option below to enable auto-approval",
280280
"toggleAriaLabel": "Toggle auto-approval",
281281
"disabledAriaLabel": "Auto-approval disabled - select options first",
282-
"triggerLabel_zero": "No auto-approve",
282+
"triggerLabelOff": "Auto-approve off",
283+
"triggerLabel_zero": "0 auto-approve",
283284
"triggerLabel_one": "1 auto-approved",
284285
"triggerLabel_other": "{{count}} auto-approved",
285286
"triggerLabelAll": "YOLO"

webview-ui/src/i18n/locales/es/chat.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/fr/chat.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/hi/chat.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)