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
6 changes: 6 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ export const globalSettingsSchema = z.object({
* @default "send"
*/
enterBehavior: z.enum(["send", "newline"]).optional(),
/**
* Multiplier for the chat view font size.
* - Min: 0.5, Max: 2.0
* @default 1.0
*/
chatFontSizeMultiplier: z.number().min(0.5).max(2).optional(),
profileThresholds: z.record(z.string(), z.number()).optional(),
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
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 @@ -299,6 +299,7 @@ export type ExtensionState = Pick<
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "enterBehavior"
| "chatFontSizeMultiplier"
| "includeCurrentTime"
| "includeCurrentCost"
| "maxGitStatusFiles"
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const commandIds = [
"acceptInput",
"focusPanel",
"toggleAutoApprove",

"increaseChatFontSize",
"decreaseChatFontSize",
"resetChatFontSize",
] as const

export type CommandId = (typeof commandIds)[number]
Expand Down
34 changes: 34 additions & 0 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,40 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
action: "toggleAutoApprove",
})
},
increaseChatFontSize: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

if (!visibleProvider) {
return
}

const currentMultiplier = visibleProvider.contextProxy.getValue("chatFontSizeMultiplier") ?? 1
const newMultiplier = Math.min(2, Math.round((currentMultiplier + 0.1) * 10) / 10)
await visibleProvider.contextProxy.setValue("chatFontSizeMultiplier", newMultiplier)
await visibleProvider.postStateToWebview()
},
decreaseChatFontSize: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

if (!visibleProvider) {
return
}

const currentMultiplier = visibleProvider.contextProxy.getValue("chatFontSizeMultiplier") ?? 1
const newMultiplier = Math.max(0.5, Math.round((currentMultiplier - 0.1) * 10) / 10)
await visibleProvider.contextProxy.setValue("chatFontSizeMultiplier", newMultiplier)
await visibleProvider.postStateToWebview()
},
resetChatFontSize: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

if (!visibleProvider) {
return
}

await visibleProvider.contextProxy.setValue("chatFontSizeMultiplier", 1)
await visibleProvider.postStateToWebview()
},
})

export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
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 @@ -2174,6 +2174,7 @@ export class ClineProvider
historyPreviewCollapsed,
reasoningBlockCollapsed,
enterBehavior,
chatFontSizeMultiplier,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
Expand Down Expand Up @@ -2300,6 +2301,7 @@ export class ClineProvider
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
enterBehavior: enterBehavior ?? "send",
chatFontSizeMultiplier: chatFontSizeMultiplier ?? 1,
cloudUserInfo,
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
cloudAuthSkipModel: this.context.globalState.get<boolean>("roo-auth-skip-model") ?? false,
Expand Down Expand Up @@ -2523,6 +2525,7 @@ export class ClineProvider
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
enterBehavior: stateValues.enterBehavior ?? "send",
chatFontSizeMultiplier: stateValues.chatFontSizeMultiplier ?? 1,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
Expand Down
15 changes: 15 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@
"command": "roo-cline.toggleAutoApprove",
"title": "%command.toggleAutoApprove.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.increaseChatFontSize",
"title": "%command.increaseChatFontSize.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.decreaseChatFontSize",
"title": "%command.decreaseChatFontSize.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.resetChatFontSize",
"title": "%command.resetChatFontSize.title%",
"category": "%configuration.title%"
}
],
"menus": {
Expand Down
3 changes: 3 additions & 0 deletions src/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"command.terminal.explainCommand.title": "Explain This Command",
"command.acceptInput.title": "Accept Input/Suggestion",
"command.toggleAutoApprove.title": "Toggle Auto-Approve",
"command.increaseChatFontSize.title": "Increase Chat Font Size",
"command.decreaseChatFontSize.title": "Decrease Chat Font Size",
"command.resetChatFontSize.title": "Reset Chat Font Size",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
Expand Down
4 changes: 3 additions & 1 deletion webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
cloudIsAuthenticated,
messageQueue = [],
showWorktreesInHomeScreen,
chatFontSizeMultiplier,
} = useExtensionState()

// Show a WarningRow when the user sends a message with a retired provider.
Expand Down Expand Up @@ -1527,7 +1528,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
return (
<div
data-testid="chat-view"
className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}
style={{ fontSize: `calc(1em * ${chatFontSizeMultiplier ?? 1})` }}>
{telemetrySetting === "unset" && <TelemetryBanner />}
{(showAnnouncement || showAnnouncementModal) && (
<Announcement
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 @@ -200,6 +200,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
openRouterImageGenerationSelectedModel,
reasoningBlockCollapsed,
enterBehavior,
chatFontSizeMultiplier,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

chatFontSizeMultiplier is destructured from cachedState here but is never included in the updatedSettings payload inside handleSubmit (around line 416, next to enterBehavior). The commands persist correctly via contextProxy.setValue(), but changes made through the Settings UI will be lost on reload because handleSubmit never sends this field to the extension host. Add chatFontSizeMultiplier: chatFontSizeMultiplier ?? 1, to the updatedSettings object in handleSubmit.

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

includeCurrentTime,
includeCurrentCost,
maxGitStatusFiles,
Expand Down Expand Up @@ -413,6 +414,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
enterBehavior: enterBehavior ?? "send",
chatFontSizeMultiplier: chatFontSizeMultiplier ?? 1,
includeCurrentTime: includeCurrentTime ?? true,
includeCurrentCost: includeCurrentCost ?? true,
maxGitStatusFiles: maxGitStatusFiles ?? 0,
Expand Down Expand Up @@ -892,6 +894,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
enterBehavior={enterBehavior ?? "send"}
chatFontSizeMultiplier={chatFontSizeMultiplier ?? 1}
setCachedStateField={setCachedStateField}
/>
)}
Expand Down
89 changes: 88 additions & 1 deletion webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HTMLAttributes, useMemo } from "react"
import { HTMLAttributes, useMemo, useState, useCallback, useEffect } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { RotateCcw } from "lucide-react"
import { telemetryClient } from "@/utils/TelemetryClient"

import { SetCachedStateField } from "./types"
Expand All @@ -12,17 +13,27 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"
interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
reasoningBlockCollapsed: boolean
enterBehavior: "send" | "newline"
chatFontSizeMultiplier: number
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}

export const UISettings = ({
reasoningBlockCollapsed,
enterBehavior,
chatFontSizeMultiplier,
setCachedStateField,
...props
}: UISettingsProps) => {
const { t } = useAppTranslation()

// Local state for the input value to allow typing freely
const [localMultiplier, setLocalMultiplier] = useState(chatFontSizeMultiplier.toString())

// Sync local state when prop changes (e.g., from commands)
useEffect(() => {
setLocalMultiplier(chatFontSizeMultiplier.toString())
}, [chatFontSizeMultiplier])

// Detect platform for dynamic modifier key display
const primaryMod = useMemo(() => {
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
Expand All @@ -48,6 +59,45 @@ export const UISettings = ({
})
}

const handleFontSizeMultiplierChange = useCallback(
(value: string) => {
setLocalMultiplier(value)

// Parse and validate the value
const numValue = parseFloat(value)
if (!isNaN(numValue)) {
// Clamp the value between 0.5 and 2
const clampedValue = Math.max(0.5, Math.min(2, numValue))
setCachedStateField("chatFontSizeMultiplier", clampedValue)
}
},
[setCachedStateField],
)

const handleFontSizeMultiplierBlur = useCallback(() => {
// On blur, ensure the display value matches the clamped value
const numValue = parseFloat(localMultiplier)
if (isNaN(numValue)) {
setLocalMultiplier(chatFontSizeMultiplier.toString())
} else {
const clampedValue = Math.max(0.5, Math.min(2, numValue))
setLocalMultiplier(clampedValue.toString())

// Track telemetry event on blur to capture only the user's final value
telemetryClient.capture("ui_settings_chat_font_size_changed", {
multiplier: clampedValue,
})
Comment on lines +83 to +89
Copy link
Contributor Author

Choose a reason for hiding this comment

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

handleFontSizeMultiplierBlur fires ui_settings_chat_font_size_changed telemetry on every blur with a valid number, even when the value hasn't actually changed (e.g., user clicks into the input and tabs away without editing). The event name implies a change occurred, but none did. This inflates the metric and makes it unreliable for measuring real setting changes. Adding a guard if (clampedValue !== chatFontSizeMultiplier) before the capture call would limit it to actual changes.

Suggested change
const clampedValue = Math.max(0.5, Math.min(2, numValue))
setLocalMultiplier(clampedValue.toString())
// Track telemetry event on blur to capture only the user's final value
telemetryClient.capture("ui_settings_chat_font_size_changed", {
multiplier: clampedValue,
})
const clampedValue = Math.max(0.5, Math.min(2, numValue))
setLocalMultiplier(clampedValue.toString())
// Only fire telemetry if the value actually changed
if (clampedValue !== chatFontSizeMultiplier) {
telemetryClient.capture("ui_settings_chat_font_size_changed", {
multiplier: clampedValue,
})
}

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

}
}, [localMultiplier, chatFontSizeMultiplier])

const handleResetFontSize = useCallback(() => {
setCachedStateField("chatFontSizeMultiplier", 1)
setLocalMultiplier("1")

// Track telemetry event
telemetryClient.capture("ui_settings_chat_font_size_reset", {})
}, [setCachedStateField])

return (
<div {...props}>
<SectionHeader>{t("settings:sections.ui")}</SectionHeader>
Expand Down Expand Up @@ -91,6 +141,43 @@ export const UISettings = ({
</div>
</div>
</SearchableSetting>

{/* Chat Font Size Multiplier Setting */}
<SearchableSetting
settingId="ui-chat-font-size"
section="ui"
label={t("settings:ui.chatFontSizeMultiplier.label")}>
<div className="flex flex-col gap-1">
<label className="font-medium" htmlFor="chat-font-size-input">
{t("settings:ui.chatFontSizeMultiplier.label")}
</label>
<div className="flex items-center gap-2">
<input
id="chat-font-size-input"
type="number"
min="0.5"
max="2"
step="0.1"
value={localMultiplier}
onChange={(e) => handleFontSizeMultiplierChange(e.target.value)}
onBlur={handleFontSizeMultiplierBlur}
className="w-20 px-2 py-1 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded"
data-testid="chat-font-size-input"
/>
<button
onClick={handleResetFontSize}
className="flex items-center gap-1 px-2 py-1 text-sm bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground rounded"
title={t("settings:ui.chatFontSizeMultiplier.reset")}
data-testid="chat-font-size-reset-button">
<RotateCcw className="w-3 h-3" />
{t("settings:ui.chatFontSizeMultiplier.reset")}
</button>
</div>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:ui.chatFontSizeMultiplier.description")}
</div>
</div>
</SearchableSetting>
</div>
</Section>
</div>
Expand Down
Loading
Loading