From d1bddd3cd3b477a4c5c104644b7549852ab9167d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 8 Feb 2026 21:24:23 +0000 Subject: [PATCH] feat: add autoExpandDiffs setting to auto-expand diffs in chat messages Adds a new boolean setting `autoExpandDiffs` (default: false) that automatically expands diff views in "Roo wants to edit this file" chat messages. Diffs are still constrained to max-h-[300px] with scrollbar. Changes: - Add autoExpandDiffs to GlobalSettings schema (packages/types) - Add autoExpandDiffs to ExtensionState type (packages/types) - Wire setting through ClineProvider state to webview - Add auto-expand logic in ChatView for diff tool messages - Add UI toggle in Settings > UI section - Add English i18n translations - Update test fixtures Closes #10955 --- packages/types/src/global-settings.ts | 5 +++ packages/types/src/vscode-extension-host.ts | 1 + src/core/webview/ClineProvider.ts | 3 ++ webview-ui/src/components/chat/ChatView.tsx | 38 +++++++++++++++++++ .../src/components/settings/SettingsView.tsx | 3 ++ .../src/components/settings/UISettings.tsx | 24 ++++++++++++ .../SettingsView.change-detection.spec.tsx | 1 + .../SettingsView.unsaved-changes.spec.tsx | 1 + .../settings/__tests__/UISettings.spec.tsx | 1 + .../src/context/ExtensionStateContext.tsx | 2 + webview-ui/src/i18n/locales/en/settings.json | 4 ++ 11 files changed, 83 insertions(+) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index fce48cfb5d5..e578a660758 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -208,6 +208,11 @@ export const globalSettingsSchema = z.object({ includeTaskHistoryInEnhance: z.boolean().optional(), historyPreviewCollapsed: z.boolean().optional(), reasoningBlockCollapsed: z.boolean().optional(), + /** + * Whether to auto-expand diffs in "Roo wants to edit this file" chat messages. + * @default false + */ + autoExpandDiffs: z.boolean().optional(), /** * Controls the keyboard behavior for sending messages in the chat input. * - "send": Enter sends message, Shift+Enter creates newline (default) diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 49a63c3ae48..b6dc46b78bc 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -328,6 +328,7 @@ export type ExtensionState = Pick< | "openRouterImageGenerationSelectedModel" | "includeTaskHistoryInEnhance" | "reasoningBlockCollapsed" + | "autoExpandDiffs" | "enterBehavior" | "includeCurrentTime" | "includeCurrentCost" diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index fc15a8dd5c3..c7ea21a7d55 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2063,6 +2063,7 @@ export class ClineProvider maxTotalImageSize, historyPreviewCollapsed, reasoningBlockCollapsed, + autoExpandDiffs, enterBehavior, cloudUserInfo, cloudIsAuthenticated, @@ -2207,6 +2208,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + autoExpandDiffs: autoExpandDiffs ?? false, enterBehavior: enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, @@ -2445,6 +2447,7 @@ export class ClineProvider maxTotalImageSize: stateValues.maxTotalImageSize ?? 20, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, + autoExpandDiffs: stateValues.autoExpandDiffs ?? false, enterBehavior: stateValues.enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated, diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 5c377eb1f59..d86de8eb233 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -97,6 +97,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction new Set(["editedExistingFile", "appliedDiff", "insertContent", "searchAndReplace", "newFileCreated"]), + [], + ) + + useEffect(() => { + if (!autoExpandDiffs) { + return + } + + const newExpansions: Record = {} + + for (const msg of modifiedMessages) { + // Skip messages already tracked in expandedRows + if (expandedRows[msg.ts] !== undefined) { + continue + } + + // Check if this message contains a diff tool + if (msg.text) { + try { + const tool = JSON.parse(msg.text) + if (tool.tool && DIFF_TOOL_TYPES.has(tool.tool)) { + newExpansions[msg.ts] = true + } + } catch { + // Not valid JSON, skip + } + } + } + + if (Object.keys(newExpansions).length > 0) { + setExpandedRows((prev) => ({ ...prev, ...newExpansions })) + } + }, [modifiedMessages, autoExpandDiffs, expandedRows, DIFF_TOOL_TYPES]) + const isStreaming = useMemo(() => { // Checking clineAsk isn't enough since messages effect may be called // again for a tool for example, set clineAsk to its value, and if the diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 1876302b472..6704e0d11cc 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -208,6 +208,7 @@ const SettingsView = forwardRef(({ onDone, t openRouterImageApiKey, openRouterImageGenerationSelectedModel, reasoningBlockCollapsed, + autoExpandDiffs, enterBehavior, includeCurrentTime, includeCurrentCost, @@ -414,6 +415,7 @@ const SettingsView = forwardRef(({ onDone, t followupAutoApproveTimeoutMs, includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + autoExpandDiffs: autoExpandDiffs ?? false, enterBehavior: enterBehavior ?? "send", includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, @@ -907,6 +909,7 @@ const SettingsView = forwardRef(({ onDone, t {renderTab === "ui" && ( diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx index a3488dc59e1..b9576b8d332 100644 --- a/webview-ui/src/components/settings/UISettings.tsx +++ b/webview-ui/src/components/settings/UISettings.tsx @@ -11,12 +11,14 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext" interface UISettingsProps extends HTMLAttributes { reasoningBlockCollapsed: boolean + autoExpandDiffs: boolean enterBehavior: "send" | "newline" setCachedStateField: SetCachedStateField } export const UISettings = ({ reasoningBlockCollapsed, + autoExpandDiffs, enterBehavior, setCachedStateField, ...props @@ -38,6 +40,10 @@ export const UISettings = ({ }) } + const handleAutoExpandDiffsChange = (value: boolean) => { + setCachedStateField("autoExpandDiffs", value) + } + const handleEnterBehaviorChange = (requireCtrlEnter: boolean) => { const newBehavior = requireCtrlEnter ? "newline" : "send" setCachedStateField("enterBehavior", newBehavior) @@ -72,6 +78,24 @@ export const UISettings = ({ + {/* Auto-Expand Diffs Setting */} + +
+ handleAutoExpandDiffsChange(e.target.checked)} + data-testid="auto-expand-diffs-checkbox"> + {t("settings:ui.autoExpandDiffs.label")} + +
+ {t("settings:ui.autoExpandDiffs.description")} +
+
+
+ {/* Enter Key Behavior Setting */} { openRouterImageApiKey: undefined, openRouterImageGenerationSelectedModel: undefined, reasoningBlockCollapsed: true, + autoExpandDiffs: false, ...overrides, }) diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx index 437404e0e7d..27a8bdd429b 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx @@ -265,6 +265,7 @@ describe("SettingsView - Unsaved Changes Detection", () => { openRouterImageApiKey: undefined, openRouterImageGenerationSelectedModel: undefined, reasoningBlockCollapsed: true, + autoExpandDiffs: false, } beforeEach(() => { diff --git a/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx b/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx index 2a21a410b38..a3b52a1f136 100644 --- a/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/UISettings.spec.tsx @@ -5,6 +5,7 @@ import { UISettings } from "../UISettings" describe("UISettings", () => { const defaultProps = { reasoningBlockCollapsed: false, + autoExpandDiffs: false, enterBehavior: "send" as const, setCachedStateField: vi.fn(), } diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 2378873f010..40951970239 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -237,6 +237,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalZdotdir: false, // Default ZDOTDIR handling setting historyPreviewCollapsed: false, // Initialize the new state (default to expanded) reasoningBlockCollapsed: true, // Default to collapsed + autoExpandDiffs: false, // Default to collapsed diffs enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline cloudUserInfo: null, cloudIsAuthenticated: false, @@ -480,6 +481,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const contextValue: ExtensionStateContextType = { ...state, reasoningBlockCollapsed: state.reasoningBlockCollapsed ?? true, + autoExpandDiffs: state.autoExpandDiffs ?? false, didHydrateState, showWelcome, theme, diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 61dfaf42af5..c293f98e8fa 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -161,6 +161,10 @@ "label": "Collapse Thinking messages by default", "description": "When enabled, thinking blocks will be collapsed by default until you interact with them" }, + "autoExpandDiffs": { + "label": "Auto-expand diffs in chat messages", + "description": "When enabled, file diffs in edit messages will be expanded by default instead of requiring a click to view" + }, "requireCtrlEnterToSend": { "label": "Require {{primaryMod}}+Enter to send messages", "description": "When enabled, you must press {{primaryMod}}+Enter to send messages instead of just Enter"