Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ export type ExtensionState = Pick<
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "autoExpandDiffs"
| "enterBehavior"
| "includeCurrentTime"
| "includeCurrentCost"
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,7 @@ export class ClineProvider
maxTotalImageSize,
historyPreviewCollapsed,
reasoningBlockCollapsed,
autoExpandDiffs,
enterBehavior,
cloudUserInfo,
cloudIsAuthenticated,
Expand Down Expand Up @@ -2207,6 +2208,7 @@ export class ClineProvider
hasSystemPromptOverride,
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
autoExpandDiffs: autoExpandDiffs ?? false,
enterBehavior: enterBehavior ?? "send",
cloudUserInfo,
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
Expand Down Expand Up @@ -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,
Expand Down
38 changes: 38 additions & 0 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
messageQueue = [],
isBrowserSessionActive,
showWorktreesInHomeScreen,
autoExpandDiffs,
} = useExtensionState()

const messagesRef = useRef(messages)
Expand Down Expand Up @@ -527,6 +528,43 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
prevExpandedRowsRef.current = expandedRows // Store current state for next comparison
}, [expandedRows])

// Auto-expand diff rows when autoExpandDiffs setting is enabled
const DIFF_TOOL_TYPES = useMemo(
() => new Set(["editedExistingFile", "appliedDiff", "insertContent", "searchAndReplace", "newFileCreated"]),
[],
)

useEffect(() => {
if (!autoExpandDiffs) {
return
}

const newExpansions: Record<number, boolean> = {}

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])
Comment on lines +537 to +566
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including expandedRows in the dependency array here means this effect re-runs on every expansion/collapse (user or auto), iterating all modifiedMessages and JSON.parse-ing each one. It also causes a double execution when it auto-expands: the setExpandedRows call updates expandedRows, which re-triggers the effect (the second run is a no-op but still wasteful). You can fix both problems by moving the logic into the functional updater of setExpandedRows and dropping expandedRows from deps:

Suggested change
useEffect(() => {
if (!autoExpandDiffs) {
return
}
const newExpansions: Record<number, boolean> = {}
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])
useEffect(() => {
if (!autoExpandDiffs) {
return
}
setExpandedRows((prev) => {
const newExpansions: Record<number, boolean> = {}
for (const msg of modifiedMessages) {
if (prev[msg.ts] !== undefined) {
continue
}
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) {
return { ...prev, ...newExpansions }
}
return prev
})
}, [modifiedMessages, autoExpandDiffs, DIFF_TOOL_TYPES])

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +537 to +566
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this effect auto-expands diffs via setExpandedRows, the existing effect at lines 509-529 will detect those transitions (from undefined to true) and set stickyFollowRef.current = false, disabling auto-scroll. It cannot distinguish between user-initiated and programmatic expansions. During an active task with autoExpandDiffs enabled, each new diff message gets auto-expanded, triggers that detection, and disables auto-scroll -- the user has to manually scroll to see subsequent messages, which undermines the purpose of this feature. Consider guarding against this by setting a ref flag (e.g. isAutoExpandingRef.current = true) before calling setExpandedRows here, and checking it in the expansion-tracking effect to skip the sticky-follow disable.

Fix it with Roo Code or mention @roomote and request a fix.


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
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
openRouterImageApiKey,
openRouterImageGenerationSelectedModel,
reasoningBlockCollapsed,
autoExpandDiffs,
enterBehavior,
includeCurrentTime,
includeCurrentCost,
Expand Down Expand Up @@ -414,6 +415,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
followupAutoApproveTimeoutMs,
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
autoExpandDiffs: autoExpandDiffs ?? false,
enterBehavior: enterBehavior ?? "send",
includeCurrentTime: includeCurrentTime ?? true,
includeCurrentCost: includeCurrentCost ?? true,
Expand Down Expand Up @@ -907,6 +909,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{renderTab === "ui" && (
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
autoExpandDiffs={autoExpandDiffs ?? false}
enterBehavior={enterBehavior ?? "send"}
setCachedStateField={setCachedStateField}
/>
Expand Down
24 changes: 24 additions & 0 deletions webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"

interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
reasoningBlockCollapsed: boolean
autoExpandDiffs: boolean
enterBehavior: "send" | "newline"
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}

export const UISettings = ({
reasoningBlockCollapsed,
autoExpandDiffs,
enterBehavior,
setCachedStateField,
...props
Expand All @@ -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)
Expand Down Expand Up @@ -72,6 +78,24 @@ export const UISettings = ({
</div>
</SearchableSetting>

{/* Auto-Expand Diffs Setting */}
<SearchableSetting
settingId="ui-auto-expand-diffs"
section="ui"
label={t("settings:ui.autoExpandDiffs.label")}>
<div className="flex flex-col gap-1">
<VSCodeCheckbox
checked={autoExpandDiffs}
onChange={(e: any) => handleAutoExpandDiffsChange(e.target.checked)}
data-testid="auto-expand-diffs-checkbox">
<span className="font-medium">{t("settings:ui.autoExpandDiffs.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm ml-5 mt-1">
{t("settings:ui.autoExpandDiffs.description")}
</div>
</div>
</SearchableSetting>

{/* Enter Key Behavior Setting */}
<SearchableSetting
settingId="ui-enter-behavior"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ describe("SettingsView - Change Detection Fix", () => {
openRouterImageApiKey: undefined,
openRouterImageGenerationSelectedModel: undefined,
reasoningBlockCollapsed: true,
autoExpandDiffs: false,
...overrides,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ describe("SettingsView - Unsaved Changes Detection", () => {
openRouterImageApiKey: undefined,
openRouterImageGenerationSelectedModel: undefined,
reasoningBlockCollapsed: true,
autoExpandDiffs: false,
}

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { UISettings } from "../UISettings"
describe("UISettings", () => {
const defaultProps = {
reasoningBlockCollapsed: false,
autoExpandDiffs: false,
enterBehavior: "send" as const,
setCachedStateField: vi.fn(),
}
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading