Skip to content

Commit fd5ecdd

Browse files
feat(chat): add compact tool UI mode for executed tool blocks (#322)
Adds an opt-in global setting (compactToolUI, off by default) that collapses executed (say) tool blocks in the chat history to a single clickable line, hiding verbose descriptions and JSON payloads until the user expands them. Approval prompts (ask) are never compacted, so users always see the parameters they are approving. Toggle lives in Context Management settings; new i18n keys added across all 18 locales. Closes #322.
1 parent b761a0a commit fd5ecdd

43 files changed

Lines changed: 218 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/types/src/global-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export const globalSettingsSchema = z.object({
163163
maxOpenTabsContext: z.number().optional(),
164164
maxWorkspaceFiles: z.number().optional(),
165165
showRooIgnoredFiles: z.boolean().optional(),
166+
compactToolUI: z.boolean().optional(),
166167
enableSubfolderRules: z.boolean().optional(),
167168
maxImageFileSize: z.number().optional(),
168169
maxTotalImageSize: z.number().optional(),

packages/types/src/vscode-extension-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ export type ExtensionState = Pick<
320320
maxOpenTabsContext: number // Maximum number of VSCode open tabs to include in context (0-500)
321321
maxWorkspaceFiles: number // Maximum number of files to include in current working directory details (0-500)
322322
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
323+
compactToolUI?: boolean // Whether to collapse executed tool blocks to a single expandable line (#322)
323324
enableSubfolderRules: boolean // Whether to load rules from subdirectories
324325
maxReadFileLine?: number // Maximum line limit for read_file tool (-1 for default)
325326
maxImageFileSize: number // Maximum size of image files to process in MB

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import {
7171
Split,
7272
ArrowRight,
7373
Check,
74+
ChevronRight,
7475
} from "lucide-react"
7576
import { cn } from "@/lib/utils"
7677
import { PathTooltip } from "../ui/PathTooltip"
@@ -182,8 +183,16 @@ export const ChatRowContent = ({
182183
}: ChatRowContentProps) => {
183184
const { t, i18n } = useTranslation()
184185

185-
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration, clineMessages, currentTaskItem } =
186-
useExtensionState()
186+
const {
187+
mcpServers,
188+
alwaysAllowMcp,
189+
currentCheckpoint,
190+
mode,
191+
apiConfiguration,
192+
clineMessages,
193+
currentTaskItem,
194+
compactToolUI,
195+
} = useExtensionState()
187196
const { info: model } = useSelectedModel(apiConfiguration)
188197
const [isEditing, setIsEditing] = useState(false)
189198
const [editedContent, setEditedContent] = useState("")
@@ -1405,6 +1414,26 @@ export const ChatRowContent = ({
14051414
const sayTool = safeJsonParse<ClineSayTool>(message.text)
14061415
if (!sayTool) return null
14071416

1417+
// Compact mode (#322): collapse executed (history) tool blocks to a single
1418+
// clickable line, hiding verbose params/payloads until expanded. Only applies
1419+
// to `say` tool rows here — approval prompts (`ask`) are never compacted.
1420+
if (compactToolUI && !isExpanded) {
1421+
const compactLabel = sayTool.path ? `${sayTool.tool}: ${sayTool.path}` : sayTool.tool
1422+
return (
1423+
<div
1424+
onClick={handleToggleExpand}
1425+
className="flex items-center gap-2 py-0.5 cursor-pointer text-vscode-descriptionForeground hover:text-vscode-foreground"
1426+
data-testid="compact-tool-row"
1427+
title={t("chat:compactTool.expandHint")}>
1428+
<ChevronRight className="w-3.5 h-3.5 shrink-0" />
1429+
<PocketKnife className="w-3.5 h-3.5 shrink-0" />
1430+
<span className="truncate text-sm">
1431+
{t("chat:compactTool.label", { tool: compactLabel })}
1432+
</span>
1433+
</div>
1434+
)
1435+
}
1436+
14081437
switch (sayTool.tool) {
14091438
case "runSlashCommand": {
14101439
const slashCommandInfo = sayTool

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type ContextManagementSettingsProps = HTMLAttributes<HTMLDivElement> & {
3232
maxOpenTabsContext: number
3333
maxWorkspaceFiles: number
3434
showRooIgnoredFiles?: boolean
35+
compactToolUI?: boolean
3536
enableSubfolderRules?: boolean
3637
maxImageFileSize?: number
3738
maxTotalImageSize?: number
@@ -50,6 +51,7 @@ type ContextManagementSettingsProps = HTMLAttributes<HTMLDivElement> & {
5051
| "maxOpenTabsContext"
5152
| "maxWorkspaceFiles"
5253
| "showRooIgnoredFiles"
54+
| "compactToolUI"
5355
| "enableSubfolderRules"
5456
| "maxImageFileSize"
5557
| "maxTotalImageSize"
@@ -70,6 +72,7 @@ export const ContextManagementSettings = ({
7072
maxOpenTabsContext,
7173
maxWorkspaceFiles,
7274
showRooIgnoredFiles,
75+
compactToolUI,
7376
enableSubfolderRules,
7477
setCachedStateField,
7578
maxImageFileSize,
@@ -229,6 +232,23 @@ export const ContextManagementSettings = ({
229232
</div>
230233
</SearchableSetting>
231234

235+
<SearchableSetting
236+
settingId="context-compact-tool-ui"
237+
section="contextManagement"
238+
label={t("settings:contextManagement.compactToolUI.label")}>
239+
<VSCodeCheckbox
240+
checked={compactToolUI}
241+
onChange={(e: any) => setCachedStateField("compactToolUI", e.target.checked)}
242+
data-testid="compact-tool-ui-checkbox">
243+
<label className="block font-medium mb-1">
244+
{t("settings:contextManagement.compactToolUI.label")}
245+
</label>
246+
</VSCodeCheckbox>
247+
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-3">
248+
{t("settings:contextManagement.compactToolUI.description")}
249+
</div>
250+
</SearchableSetting>
251+
232252
<SearchableSetting
233253
settingId="context-enable-subfolder-rules"
234254
section="contextManagement"

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
185185
terminalZdotdir,
186186
writeDelayMs,
187187
showRooIgnoredFiles,
188+
compactToolUI,
188189
enableSubfolderRules,
189190
maxImageFileSize,
190191
maxTotalImageSize,
@@ -834,6 +835,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
834835
maxOpenTabsContext={maxOpenTabsContext}
835836
maxWorkspaceFiles={maxWorkspaceFiles ?? 200}
836837
showRooIgnoredFiles={showRooIgnoredFiles}
838+
compactToolUI={compactToolUI ?? false}
837839
enableSubfolderRules={enableSubfolderRules}
838840
maxImageFileSize={maxImageFileSize}
839841
maxTotalImageSize={maxTotalImageSize}

webview-ui/src/components/settings/__tests__/ContextManagementSettings.spec.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ describe("ContextManagementSettings", () => {
9292
maxOpenTabsContext: 20,
9393
maxWorkspaceFiles: 200,
9494
showRooIgnoredFiles: false,
95+
compactToolUI: false,
9596
profileThresholds: {},
9697
includeDiagnosticMessages: true,
9798
maxDiagnosticMessages: 50,
@@ -150,6 +151,23 @@ describe("ContextManagementSettings", () => {
150151
})
151152
})
152153

154+
it("renders the compact tool UI toggle", () => {
155+
render(<ContextManagementSettings {...defaultProps} />)
156+
expect(screen.getByTestId("compact-tool-ui-checkbox")).toBeInTheDocument()
157+
})
158+
159+
it("calls setCachedStateField when the compact tool UI checkbox is toggled", async () => {
160+
const setCachedStateField = vi.fn()
161+
render(<ContextManagementSettings {...defaultProps} setCachedStateField={setCachedStateField} />)
162+
163+
const checkbox = screen.getByTestId("compact-tool-ui-checkbox").querySelector("input")!
164+
fireEvent.click(checkbox)
165+
166+
await waitFor(() => {
167+
expect(setCachedStateField).toHaveBeenCalledWith("compactToolUI", true)
168+
})
169+
})
170+
153171
it("calls setCachedStateField when max diagnostic messages slider is changed", async () => {
154172
const setCachedStateField = vi.fn()
155173
render(<ContextManagementSettings {...defaultProps} setCachedStateField={setCachedStateField} />)

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
224224
cwd: "",
225225
telemetrySetting: "unset",
226226
showRooIgnoredFiles: true, // Default to showing .rooignore'd files with lock symbol (current behavior).
227+
compactToolUI: false, // Default off — opt-in single-line collapsed tool blocks (#322).
227228
enableSubfolderRules: false, // Default to disabled - must be enabled to load rules from subdirectories
228229
renderContext: "sidebar",
229230
maxReadFileLine: -1, // Default max line limit for read_file tool (-1 for default)

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

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

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

Lines changed: 4 additions & 0 deletions
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: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)