diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index e8dbffb62d..b1a4db468e 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -148,6 +148,7 @@ export const globalSettingsSchema = z.object({ hasOpenedModeSelector: z.boolean().optional(), lastModeExportPath: z.string().optional(), lastModeImportPath: z.string().optional(), + uiFontSize: z.number().min(50).max(200).optional(), // UI font size as percentage (50-200%) }) export type GlobalSettings = z.infer diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 970c9d0712..c51eb13bbc 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2657,5 +2657,12 @@ export const webviewMessageHandler = async ( vscode.window.showWarningMessage(t("common:mdm.info.organization_requires_auth")) break } + case "uiFontSize": { + // Store the UI font size preference + const fontSize = message.value ?? 100 + await updateGlobalState("uiFontSize", fontSize) + await provider.postStateToWebview() + break + } } } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 65fe181859..8c0d62b678 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -326,6 +326,7 @@ export type ExtensionState = Pick< marketplaceInstalledMetadata?: { project: Record; global: Record } profileThresholds: Record hasOpenedModeSelector: boolean + uiFontSize?: number // UI font size as percentage (50-200%) } export interface ClineSayTool { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index e2df805340..41724d20e8 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -212,6 +212,7 @@ export interface WebviewMessage { | "createCommand" | "insertTextIntoTextarea" | "showMdmAuthRequiredNotification" + | "uiFontSize" text?: string editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index f24e4556a1..8a7ac10903 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -74,6 +74,8 @@ const App = () => { cloudApiUrl, renderContext, mdmCompliant, + uiFontSize, + setUiFontSize, } = useExtensionState() // Create a persistent state manager @@ -226,6 +228,43 @@ const App = () => { } }, [tab]) + // Handle Ctrl + Mouse Wheel for font size adjustment + useEffect(() => { + const handleWheel = (e: WheelEvent) => { + // Check if Ctrl key is pressed (or Cmd on Mac) + if (e.ctrlKey || e.metaKey) { + e.preventDefault() // Prevent default browser zoom + + const currentSize = uiFontSize || 100 + const delta = e.deltaY > 0 ? -5 : 5 // Decrease on scroll down, increase on scroll up + const newSize = Math.min(200, Math.max(50, currentSize + delta)) // Clamp between 50% and 200% + + if (newSize !== currentSize) { + setUiFontSize(newSize) + // Send message to extension to persist the setting + vscode.postMessage({ type: "uiFontSize", value: newSize }) + } + } + } + + // Add event listener with passive: false to allow preventDefault + window.addEventListener("wheel", handleWheel, { passive: false }) + + return () => { + window.removeEventListener("wheel", handleWheel) + } + }, [uiFontSize, setUiFontSize]) + + // Apply font size to the document + useEffect(() => { + if (uiFontSize) { + // Apply the font size as a CSS variable that can be used throughout the app + document.documentElement.style.setProperty("--ui-font-scale", `${uiFontSize / 100}`) + // Also apply a direct font-size to the body for immediate effect + document.documentElement.style.fontSize = `${uiFontSize}%` + } + }, [uiFontSize]) + if (!didHydrateState) { return null } diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index bd335d7b2d..e0d5e17f71 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -151,6 +151,8 @@ export interface ExtensionStateContextType extends ExtensionState { setMaxDiagnosticMessages: (value: number) => void includeTaskHistoryInEnhance?: boolean setIncludeTaskHistoryInEnhance: (value: boolean) => void + uiFontSize?: number + setUiFontSize: (value: number) => void } export const ExtensionStateContext = createContext(undefined) @@ -239,6 +241,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode autoCondenseContext: true, autoCondenseContextPercent: 100, profileThresholds: {}, + uiFontSize: 100, // Default UI font size as percentage (100 = 100% = normal size) codebaseIndexConfig: { codebaseIndexEnabled: true, codebaseIndexQdrantUrl: "http://localhost:6333", @@ -271,6 +274,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode global: {}, }) const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true) + const [, setUiFontSizeState] = useState(100) // Default to 100% const setListApiConfigMeta = useCallback( (value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })), @@ -308,6 +312,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode if ((newState as any).includeTaskHistoryInEnhance !== undefined) { setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance) } + // Update uiFontSize if present in state message + if (newState.uiFontSize !== undefined) { + setUiFontSizeState(newState.uiFontSize) + } // Handle marketplace data if present in state message if (newState.marketplaceItems !== undefined) { setMarketplaceItems(newState.marketplaceItems) @@ -524,6 +532,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }, includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance, + uiFontSize: state.uiFontSize ?? 100, + setUiFontSize: (value) => { + setState((prevState) => ({ ...prevState, uiFontSize: value })) + setUiFontSizeState(value) + }, } return {children}