diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx
index 2e987b7c497..9a0b7677e61 100644
--- a/webview-ui/src/components/chat/AutoApproveMenu.tsx
+++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx
@@ -129,10 +129,11 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setIsExpanded((prev) => !prev)
}, [])
- // Disable main checkbox while menu is open or no options selected
+ // Disable main checkbox only when no options are selected
+ // Allow toggling even when menu is expanded for better UX and safety
const isCheckboxDisabled = useMemo(() => {
- return !hasEnabledOptions || isExpanded
- }, [hasEnabledOptions, isExpanded])
+ return !hasEnabledOptions
+ }, [hasEnabledOptions])
const enabledActionsList = Object.entries(toggles)
.filter(([_key, value]) => !!value)
@@ -184,14 +185,14 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
? t("chat:autoApprove.toggleAriaLabel")
: t("chat:autoApprove.disabledAriaLabel")
}
- onChange={() => {
+ onChange={useCallback(() => {
if (hasEnabledOptions) {
const newValue = !(autoApprovalEnabled ?? false)
setAutoApprovalEnabled(newValue)
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
}
// If no options enabled, do nothing
- }}
+ }, [hasEnabledOptions, autoApprovalEnabled, setAutoApprovalEnabled])}
/>
diff --git a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx
index 185e5eeec6e..9c98a1d839e 100644
--- a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx
@@ -13,6 +13,35 @@ vi.mock("@src/utils/vscode", () => ({
// Mock ExtensionStateContext
vi.mock("@src/context/ExtensionStateContext")
+// Mock VSCode webview UI toolkit
+vi.mock("@vscode/webview-ui-toolkit/react", () => ({
+ VSCodeCheckbox: ({ children, checked, disabled, onChange, ...props }: any) => (
+
+ ),
+ VSCodeLink: ({ children, onClick, ...props }: any) => (
+
+ {children}
+
+ ),
+ VSCodeTextField: ({ value, onInput, ...props }: any) => (
+
+ ),
+}))
+
// Mock translation hook
vi.mock("@src/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
@@ -303,5 +332,76 @@ describe("AutoApproveMenu", () => {
bool: false,
})
})
+
+ it("should keep master checkbox responsive when menu is expanded", async () => {
+ const mockSetAutoApprovalEnabled = vi.fn()
+
+ ;(useExtensionState as ReturnType).mockReturnValue({
+ ...defaultExtensionState,
+ autoApprovalEnabled: true,
+ alwaysAllowReadOnly: true,
+ setAutoApprovalEnabled: mockSetAutoApprovalEnabled,
+ })
+
+ render()
+
+ // Expand the menu
+ const menuContainer = screen.getByText("Auto-approve").parentElement
+ fireEvent.click(menuContainer!)
+
+ // Wait for the menu to expand
+ await waitFor(() => {
+ expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument()
+ })
+
+ // The master checkbox should still be enabled and clickable
+ const masterCheckbox = screen.getByRole("checkbox")
+ expect(masterCheckbox).not.toBeDisabled()
+
+ // Click the master checkbox while menu is expanded
+ fireEvent.click(masterCheckbox)
+
+ // Should successfully toggle the master checkbox
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ type: "autoApprovalEnabled",
+ bool: false,
+ })
+ expect(mockSetAutoApprovalEnabled).toHaveBeenCalledWith(false)
+ })
+
+ it("should not flicker when state changes rapidly", async () => {
+ const mockSetAutoApprovalEnabled = vi.fn()
+
+ // Start with enabled state
+ const enabledState = {
+ ...defaultExtensionState,
+ autoApprovalEnabled: true,
+ alwaysAllowReadOnly: true,
+ setAutoApprovalEnabled: mockSetAutoApprovalEnabled,
+ }
+
+ ;(useExtensionState as ReturnType).mockReturnValue(enabledState)
+
+ const { rerender } = render()
+
+ const masterCheckbox = screen.getByRole("checkbox")
+ expect(masterCheckbox).toBeChecked()
+
+ // Simulate rapid state changes
+ for (let i = 0; i < 5; i++) {
+ const newState = {
+ ...defaultExtensionState,
+ autoApprovalEnabled: i % 2 === 0,
+ alwaysAllowReadOnly: true,
+ setAutoApprovalEnabled: mockSetAutoApprovalEnabled,
+ }
+ ;(useExtensionState as ReturnType).mockReturnValue(newState)
+ rerender()
+ }
+
+ // Checkbox should reflect the final state without issues (i=4, so 4 % 2 === 0, which is true)
+ expect(masterCheckbox).toBeChecked()
+ expect(masterCheckbox).not.toBeDisabled()
+ })
})
})