diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 7e79855f7e..370256c44c 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -151,6 +151,9 @@ export const globalSettingsSchema = z.object({ hasOpenedModeSelector: z.boolean().optional(), lastModeExportPath: z.string().optional(), lastModeImportPath: z.string().optional(), + + // Interface settings + interfaceTextSize: z.enum(["small", "medium", "large"]).optional(), }) export type GlobalSettings = z.infer diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index accb66f6e9..06360cab7e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1558,6 +1558,10 @@ export const webviewMessageHandler = async ( await updateGlobalState("language", message.text as Language) await provider.postStateToWebview() break + case "interfaceTextSize": + await updateGlobalState("interfaceTextSize", message.text as "small" | "medium" | "large" | undefined) + await provider.postStateToWebview() + break case "openRouterImageApiKey": await provider.contextProxy.setValue("openRouterImageApiKey", message.text) await provider.postStateToWebview() diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 93d0b9bc45..6a0aa2d4d7 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -168,6 +168,7 @@ export interface WebviewMessage { | "browserConnectionResult" | "remoteBrowserEnabled" | "language" + | "interfaceTextSize" | "maxReadFileLine" | "maxImageFileSize" | "maxTotalImageSize" diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index fa38a566e7..65250fd36a 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -78,6 +78,7 @@ const App = () => { cloudApiUrl, renderContext, mdmCompliant, + interfaceTextSize, } = useExtensionState() // Create a persistent state manager @@ -245,7 +246,7 @@ const App = () => { // Do not conditionally load ChatView, it's expensive and there's state we // don't want to lose (user input, disableInput, askResponse promise, etc.) - return showWelcome ? ( + const content = showWelcome ? ( ) : ( <> @@ -345,6 +346,9 @@ const App = () => { )} ) + + // Wrap content with interface text size class + return
{content}
} const queryClient = new QueryClient() diff --git a/webview-ui/src/components/settings/InterfaceSettings.tsx b/webview-ui/src/components/settings/InterfaceSettings.tsx new file mode 100644 index 0000000000..74ec267e40 --- /dev/null +++ b/webview-ui/src/components/settings/InterfaceSettings.tsx @@ -0,0 +1,48 @@ +import React from "react" +import { Type } from "lucide-react" +import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { SetCachedStateField } from "./types" +import { SectionHeader } from "./SectionHeader" +import { Section } from "./Section" + +interface InterfaceSettingsProps { + interfaceTextSize?: "small" | "medium" | "large" + setCachedStateField: SetCachedStateField<"interfaceTextSize"> +} + +export const InterfaceSettings = ({ interfaceTextSize = "medium", setCachedStateField }: InterfaceSettingsProps) => { + const { t } = useAppTranslation() + + return ( +
+ +
+ +
{t("settings:interface.title")}
+
+
+ +
+
+ + { + const value = (e.target as HTMLSelectElement).value as "small" | "medium" | "large" + setCachedStateField("interfaceTextSize", value) + }}> + {t("settings:interface.textSize.small")} + {t("settings:interface.textSize.medium")} + {t("settings:interface.textSize.large")} + +

+ {t("settings:interface.textSize.description")} +

+
+
+
+ ) +} diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 66b7ff680f..526ea70404 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -24,6 +24,7 @@ import { MessageSquare, LucideIcon, SquareSlash, + Type, } from "lucide-react" import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types" @@ -66,6 +67,7 @@ import { About } from "./About" import { Section } from "./Section" import PromptsSettings from "./PromptsSettings" import { SlashCommandsSettings } from "./SlashCommandsSettings" +import { InterfaceSettings } from "./InterfaceSettings" export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden" export const settingsTabList = @@ -89,6 +91,7 @@ const sectionNames = [ "terminal", "prompts", "experimental", + "interface", "language", "about", ] as const @@ -191,6 +194,7 @@ const SettingsView = forwardRef(({ onDone, t includeTaskHistoryInEnhance, openRouterImageApiKey, openRouterImageGenerationSelectedModel, + interfaceTextSize, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -372,6 +376,7 @@ const SettingsView = forwardRef(({ onDone, t type: "openRouterImageGenerationSelectedModel", text: openRouterImageGenerationSelectedModel, }) + vscode.postMessage({ type: "interfaceTextSize", text: interfaceTextSize }) setChangeDetected(false) } } @@ -459,6 +464,7 @@ const SettingsView = forwardRef(({ onDone, t { id: "terminal", icon: SquareTerminal }, { id: "prompts", icon: MessageSquare }, { id: "experimental", icon: FlaskConical }, + { id: "interface", icon: Type }, { id: "language", icon: Globe }, { id: "about", icon: Info }, ], @@ -773,6 +779,14 @@ const SettingsView = forwardRef(({ onDone, t /> )} + {/* Interface Section */} + {activeTab === "interface" && ( + + )} + {/* Language Section */} {activeTab === "language" && ( diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 5534686db6..67f0f35477 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -155,6 +155,8 @@ export interface ExtensionStateContextType extends ExtensionState { setMaxDiagnosticMessages: (value: number) => void includeTaskHistoryInEnhance?: boolean setIncludeTaskHistoryInEnhance: (value: boolean) => void + interfaceTextSize?: "small" | "medium" | "large" + setInterfaceTextSize: (value: "small" | "medium" | "large") => void } export const ExtensionStateContext = createContext(undefined) @@ -280,6 +282,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode global: {}, }) const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true) + const [interfaceTextSize, setInterfaceTextSize] = useState<"small" | "medium" | "large">("medium") const setListApiConfigMeta = useCallback( (value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })), @@ -317,6 +320,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode if ((newState as any).includeTaskHistoryInEnhance !== undefined) { setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance) } + // Update interfaceTextSize if present in state message + if ((newState as any).interfaceTextSize !== undefined) { + setInterfaceTextSize((newState as any).interfaceTextSize) + } // Handle marketplace data if present in state message if (newState.marketplaceItems !== undefined) { setMarketplaceItems(newState.marketplaceItems) @@ -538,6 +545,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }, includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance, + interfaceTextSize, + setInterfaceTextSize, } return {children} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 1be824b37e..da8d8ec2cc 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -31,6 +31,7 @@ "slashCommands": "Slash Commands", "prompts": "Prompts", "experimental": "Experimental", + "interface": "Interface", "language": "Language", "about": "About Roo Code" }, @@ -875,5 +876,15 @@ "output": "Output", "cacheReads": "Cache reads" } + }, + "interface": { + "title": "Interface", + "textSize": { + "label": "Text Size", + "description": "Adjust the text size for Roo Code's interface. This only affects Roo Code's UI and does not change VS Code's zoom level or editor font size.", + "small": "Small", + "medium": "Medium (Default)", + "large": "Large" + } } } diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index 6f23892ced..d546dde266 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -25,6 +25,7 @@ @theme { --font-display: var(--vscode-font-family); + /* Default text sizes (medium) */ --text-xs: calc(var(--vscode-font-size) * 0.85); --text-sm: calc(var(--vscode-font-size) * 0.9); --text-base: var(--vscode-font-size); @@ -490,3 +491,22 @@ input[cmdk-input]:focus { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; } + +/* Interface text size classes */ +.interface-text-small { + --text-xs: calc(var(--vscode-font-size) * 0.75); + --text-sm: calc(var(--vscode-font-size) * 0.8); + --text-base: calc(var(--vscode-font-size) * 0.85); + --text-lg: calc(var(--vscode-font-size) * 0.95); +} + +.interface-text-medium { + /* Default sizes - already defined above */ +} + +.interface-text-large { + --text-xs: calc(var(--vscode-font-size) * 0.95); + --text-sm: calc(var(--vscode-font-size) * 1); + --text-base: calc(var(--vscode-font-size) * 1.15); + --text-lg: calc(var(--vscode-font-size) * 1.25); +}