diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx index 53a2b81a..7ea8a693 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx @@ -83,7 +83,7 @@ export const OpenChamberPage: React.FC = ({ section }) => // Visual section: Theme Mode, Font Size, Spacing, Corner Radius, Input Bar Offset (mobile) const VisualSectionContent: React.FC = () => { - return ; + return ; }; // Chat section: Default Tool Output, Diff layout, Show reasoning traces, Queue mode diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 56bf553b..37a8c465 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -82,7 +82,7 @@ const DIFF_VIEW_MODE_OPTIONS: Option<'single' | 'stacked'>[] = [ }, ]; -export type VisibleSetting = 'theme' | 'fontSize' | 'spacing' | 'cornerRadius' | 'inputBarOffset' | 'toolOutput' | 'diffLayout' | 'dotfiles' | 'reasoning' | 'queueMode' | 'textJustificationActivity' | 'terminalQuickKeys'; +export type VisibleSetting = 'theme' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'cornerRadius' | 'inputBarOffset' | 'toolOutput' | 'diffLayout' | 'dotfiles' | 'reasoning' | 'queueMode' | 'textJustificationActivity' | 'terminalQuickKeys'; interface OpenChamberVisualSettingsProps { /** Which settings to show. If undefined, shows all. */ @@ -100,6 +100,8 @@ export const OpenChamberVisualSettings: React.FC const setToolCallExpansion = useUIStore(state => state.setToolCallExpansion); const fontSize = useUIStore(state => state.fontSize); const setFontSize = useUIStore(state => state.setFontSize); + const terminalFontSize = useUIStore(state => state.terminalFontSize); + const setTerminalFontSize = useUIStore(state => state.setTerminalFontSize); const padding = useUIStore(state => state.padding); const setPadding = useUIStore(state => state.setPadding); const cornerRadius = useUIStore(state => state.cornerRadius); @@ -284,6 +286,78 @@ export const OpenChamberVisualSettings: React.FC )} + {shouldShow('terminalFontSize') && ( +
+
+

+ Terminal Font Size +

+ +
+ {isMobile ? ( +
+ setTerminalFontSize(Number(e.target.value))} + className="flex-1 min-w-0 h-3 bg-muted rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary [&::-moz-range-thumb]:border-0" + aria-label="Terminal font size" + /> + + + {terminalFontSize}px + + + setTerminalFontSize(13)} + disabled={terminalFontSize === 13} + className="h-8 w-8 px-0 border border-border bg-background hover:bg-interactive-hover disabled:opacity-100 disabled:bg-background" + aria-label="Reset terminal font size" + title="Reset" + > + + +
+ ) : ( +
+ setTerminalFontSize(Number(e.target.value))} + className="flex-1 min-w-0 h-2 bg-muted rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-primary [&::-moz-range-thumb]:border-0" + /> + + setTerminalFontSize(13)} + disabled={terminalFontSize === 13} + className="h-8 w-8 px-0 border border-border bg-background hover:bg-interactive-hover disabled:opacity-100 disabled:bg-background" + aria-label="Reset terminal font size" + title="Reset" + > + + +
+ )} +
+ )} + {shouldShow('spacing') && (
diff --git a/packages/ui/src/components/views/TerminalView.tsx b/packages/ui/src/components/views/TerminalView.tsx index 22232a5f..f9bcffbb 100644 --- a/packages/ui/src/components/views/TerminalView.tsx +++ b/packages/ui/src/components/views/TerminalView.tsx @@ -19,8 +19,6 @@ import { useDeviceInfo } from '@/lib/device'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { isDesktopRuntime, isWebRuntime } from '@/lib/desktop'; -const TERMINAL_FONT_SIZE = 13; - type Modifier = 'ctrl' | 'cmd'; type MobileKey = | 'esc' @@ -79,6 +77,7 @@ export const TerminalView: React.FC = () => { const { terminal } = useRuntimeAPIs(); const { currentTheme } = useThemeSystem(); const { monoFont } = useFontPreferences(); + const terminalFontSize = useUIStore(state => state.terminalFontSize); const { isMobile, hasTouchInput } = useDeviceInfo(); const enableTabs = !isMobile && (isWebRuntime() || isDesktopRuntime()); const showTerminalQuickKeysOnDesktop = useUIStore((state) => state.showTerminalQuickKeysOnDesktop); @@ -973,7 +972,7 @@ export const TerminalView: React.FC = () => { onResize={handleViewportResize} theme={xtermTheme} fontFamily={resolvedFontStack} - fontSize={TERMINAL_FONT_SIZE} + fontSize={terminalFontSize} enableTouchScroll={hasTouchInput} /> ) : ( @@ -989,7 +988,7 @@ export const TerminalView: React.FC = () => { onResize={handleViewportResize} theme={xtermTheme} fontFamily={resolvedFontStack} - fontSize={TERMINAL_FONT_SIZE} + fontSize={terminalFontSize} enableTouchScroll={hasTouchInput} /> diff --git a/packages/ui/src/lib/api/types.ts b/packages/ui/src/lib/api/types.ts index a8733da3..61d47906 100644 --- a/packages/ui/src/lib/api/types.ts +++ b/packages/ui/src/lib/api/types.ts @@ -403,6 +403,7 @@ export interface SettingsPayload { gitmojiEnabled?: boolean; toolCallExpansion?: 'collapsed' | 'activity' | 'detailed'; fontSize?: number; + terminalFontSize?: number; padding?: number; cornerRadius?: number; inputBarOffset?: number; diff --git a/packages/ui/src/lib/appearanceAutoSave.ts b/packages/ui/src/lib/appearanceAutoSave.ts index 7ee621ea..1de5adf5 100644 --- a/packages/ui/src/lib/appearanceAutoSave.ts +++ b/packages/ui/src/lib/appearanceAutoSave.ts @@ -12,6 +12,7 @@ type AppearanceSlice = { autoDeleteAfterDays: number; toolCallExpansion: 'collapsed' | 'activity' | 'detailed'; fontSize: number; + terminalFontSize: number; padding: number; cornerRadius: number; inputBarOffset: number; @@ -38,6 +39,7 @@ export const startAppearanceAutoSave = (): void => { autoDeleteAfterDays: useUIStore.getState().autoDeleteAfterDays, toolCallExpansion: useUIStore.getState().toolCallExpansion, fontSize: useUIStore.getState().fontSize, + terminalFontSize: useUIStore.getState().terminalFontSize, padding: useUIStore.getState().padding, cornerRadius: useUIStore.getState().cornerRadius, inputBarOffset: useUIStore.getState().inputBarOffset, @@ -76,6 +78,7 @@ export const startAppearanceAutoSave = (): void => { autoDeleteAfterDays: state.autoDeleteAfterDays, toolCallExpansion: state.toolCallExpansion, fontSize: state.fontSize, + terminalFontSize: state.terminalFontSize, padding: state.padding, cornerRadius: state.cornerRadius, inputBarOffset: state.inputBarOffset, @@ -112,6 +115,9 @@ export const startAppearanceAutoSave = (): void => { if (current.fontSize !== previous.fontSize) { diff.fontSize = current.fontSize; } + if (current.terminalFontSize !== previous.terminalFontSize) { + diff.terminalFontSize = current.terminalFontSize; + } if (current.padding !== previous.padding) { diff.padding = current.padding; } diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts index e17ac6c5..28d4c5fa 100644 --- a/packages/ui/src/lib/desktop.ts +++ b/packages/ui/src/lib/desktop.ts @@ -70,6 +70,7 @@ export type DesktopSettings = { gitmojiEnabled?: boolean; toolCallExpansion?: 'collapsed' | 'activity' | 'detailed'; fontSize?: number; + terminalFontSize?: number; padding?: number; cornerRadius?: number; inputBarOffset?: number; diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts index f1bdab45..0b9195a4 100644 --- a/packages/ui/src/lib/persistence.ts +++ b/packages/ui/src/lib/persistence.ts @@ -228,6 +228,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => { if (typeof settings.fontSize === 'number' && Number.isFinite(settings.fontSize) && settings.fontSize !== store.fontSize) { store.setFontSize(settings.fontSize); } + if (typeof settings.terminalFontSize === 'number' && Number.isFinite(settings.terminalFontSize) && settings.terminalFontSize !== store.terminalFontSize) { + store.setTerminalFontSize(settings.terminalFontSize); + } if (typeof settings.padding === 'number' && Number.isFinite(settings.padding) && settings.padding !== store.padding) { store.setPadding(settings.padding); } @@ -376,6 +379,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (typeof candidate.fontSize === 'number' && Number.isFinite(candidate.fontSize)) { result.fontSize = candidate.fontSize; } + if (typeof candidate.terminalFontSize === 'number' && Number.isFinite(candidate.terminalFontSize)) { + result.terminalFontSize = candidate.terminalFontSize; + } if (typeof candidate.padding === 'number' && Number.isFinite(candidate.padding)) { result.padding = candidate.padding; } diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 2ae1d413..429b6026 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -51,6 +51,7 @@ interface UIStore { toolCallExpansion: 'collapsed' | 'activity' | 'detailed'; fontSize: number; + terminalFontSize: number; padding: number; cornerRadius: number; inputBarOffset: number; @@ -104,6 +105,7 @@ interface UIStore { setMemoryLimitActiveSession: (value: number) => void; setToolCallExpansion: (value: 'collapsed' | 'activity' | 'detailed') => void; setFontSize: (size: number) => void; + setTerminalFontSize: (size: number) => void; setPadding: (size: number) => void; setCornerRadius: (radius: number) => void; setInputBarOffset: (offset: number) => void; @@ -168,6 +170,7 @@ export const useUIStore = create()( memoryLimitActiveSession: 180, toolCallExpansion: 'collapsed', fontSize: 100, + terminalFontSize: 13, padding: 100, cornerRadius: 12, inputBarOffset: 0, @@ -403,6 +406,12 @@ export const useUIStore = create()( get().applyTypography(); }, + setTerminalFontSize: (size) => { + const rounded = Math.round(size); + const clamped = Math.max(9, Math.min(52, rounded)); + set({ terminalFontSize: clamped }); + }, + setPadding: (size) => { // Clamp between 50% and 200% const clampedSize = Math.max(50, Math.min(200, size)); @@ -680,6 +689,7 @@ export const useUIStore = create()( memoryLimitActiveSession: state.memoryLimitActiveSession, toolCallExpansion: state.toolCallExpansion, fontSize: state.fontSize, + terminalFontSize: state.terminalFontSize, padding: state.padding, cornerRadius: state.cornerRadius, favoriteModels: state.favoriteModels, diff --git a/packages/web/server/index.js b/packages/web/server/index.js index c501dc02..8d0141da 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -1034,6 +1034,9 @@ const sanitizeSettingsUpdate = (payload) => { if (typeof candidate.fontSize === 'number' && Number.isFinite(candidate.fontSize)) { result.fontSize = Math.max(50, Math.min(200, Math.round(candidate.fontSize))); } + if (typeof candidate.terminalFontSize === 'number' && Number.isFinite(candidate.terminalFontSize)) { + result.terminalFontSize = Math.max(9, Math.min(52, Math.round(candidate.terminalFontSize))); + } if (typeof candidate.padding === 'number' && Number.isFinite(candidate.padding)) { result.padding = Math.max(50, Math.min(200, Math.round(candidate.padding))); }