feat(settings): add terminal font size controls (#294)
* feat(settings): add terminal font size configuration * chore(desktop): drop terminal font size setting
This commit is contained in:
@@ -83,7 +83,7 @@ export const OpenChamberPage: React.FC<OpenChamberPageProps> = ({ section }) =>
|
|||||||
|
|
||||||
// Visual section: Theme Mode, Font Size, Spacing, Corner Radius, Input Bar Offset (mobile)
|
// Visual section: Theme Mode, Font Size, Spacing, Corner Radius, Input Bar Offset (mobile)
|
||||||
const VisualSectionContent: React.FC = () => {
|
const VisualSectionContent: React.FC = () => {
|
||||||
return <OpenChamberVisualSettings visibleSettings={['theme', 'fontSize', 'spacing', 'cornerRadius', 'inputBarOffset', 'terminalQuickKeys']} />;
|
return <OpenChamberVisualSettings visibleSettings={['theme', 'fontSize', 'terminalFontSize', 'spacing', 'cornerRadius', 'inputBarOffset', 'terminalQuickKeys']} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Chat section: Default Tool Output, Diff layout, Show reasoning traces, Queue mode
|
// Chat section: Default Tool Output, Diff layout, Show reasoning traces, Queue mode
|
||||||
|
|||||||
@@ -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 {
|
interface OpenChamberVisualSettingsProps {
|
||||||
/** Which settings to show. If undefined, shows all. */
|
/** Which settings to show. If undefined, shows all. */
|
||||||
@@ -100,6 +100,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
const setToolCallExpansion = useUIStore(state => state.setToolCallExpansion);
|
const setToolCallExpansion = useUIStore(state => state.setToolCallExpansion);
|
||||||
const fontSize = useUIStore(state => state.fontSize);
|
const fontSize = useUIStore(state => state.fontSize);
|
||||||
const setFontSize = useUIStore(state => state.setFontSize);
|
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 padding = useUIStore(state => state.padding);
|
||||||
const setPadding = useUIStore(state => state.setPadding);
|
const setPadding = useUIStore(state => state.setPadding);
|
||||||
const cornerRadius = useUIStore(state => state.cornerRadius);
|
const cornerRadius = useUIStore(state => state.cornerRadius);
|
||||||
@@ -284,6 +286,78 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{shouldShow('terminalFontSize') && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="typography-ui-header font-semibold text-foreground">
|
||||||
|
Terminal Font Size
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{isMobile ? (
|
||||||
|
<div className="flex items-center gap-2 w-full">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="9"
|
||||||
|
max="52"
|
||||||
|
step="1"
|
||||||
|
value={terminalFontSize}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className="typography-ui-label font-medium text-foreground tabular-nums rounded-md border border-border bg-background px-2 py-1.5 min-w-[3.75rem] text-center">
|
||||||
|
{terminalFontSize}px
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ButtonSmall
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => 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"
|
||||||
|
>
|
||||||
|
<RiRestartLine className="h-3.5 w-3.5" />
|
||||||
|
</ButtonSmall>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-3 w-full max-w-md">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="9"
|
||||||
|
max="52"
|
||||||
|
step="1"
|
||||||
|
value={terminalFontSize}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
value={terminalFontSize}
|
||||||
|
onValueChange={setTerminalFontSize}
|
||||||
|
min={9}
|
||||||
|
max={52}
|
||||||
|
step={1}
|
||||||
|
aria-label="Terminal font size"
|
||||||
|
/>
|
||||||
|
<ButtonSmall
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => 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"
|
||||||
|
>
|
||||||
|
<RiRestartLine className="h-3.5 w-3.5" />
|
||||||
|
</ButtonSmall>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{shouldShow('spacing') && (
|
{shouldShow('spacing') && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import { useDeviceInfo } from '@/lib/device';
|
|||||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||||
import { isDesktopRuntime, isWebRuntime } from '@/lib/desktop';
|
import { isDesktopRuntime, isWebRuntime } from '@/lib/desktop';
|
||||||
|
|
||||||
const TERMINAL_FONT_SIZE = 13;
|
|
||||||
|
|
||||||
type Modifier = 'ctrl' | 'cmd';
|
type Modifier = 'ctrl' | 'cmd';
|
||||||
type MobileKey =
|
type MobileKey =
|
||||||
| 'esc'
|
| 'esc'
|
||||||
@@ -79,6 +77,7 @@ export const TerminalView: React.FC = () => {
|
|||||||
const { terminal } = useRuntimeAPIs();
|
const { terminal } = useRuntimeAPIs();
|
||||||
const { currentTheme } = useThemeSystem();
|
const { currentTheme } = useThemeSystem();
|
||||||
const { monoFont } = useFontPreferences();
|
const { monoFont } = useFontPreferences();
|
||||||
|
const terminalFontSize = useUIStore(state => state.terminalFontSize);
|
||||||
const { isMobile, hasTouchInput } = useDeviceInfo();
|
const { isMobile, hasTouchInput } = useDeviceInfo();
|
||||||
const enableTabs = !isMobile && (isWebRuntime() || isDesktopRuntime());
|
const enableTabs = !isMobile && (isWebRuntime() || isDesktopRuntime());
|
||||||
const showTerminalQuickKeysOnDesktop = useUIStore((state) => state.showTerminalQuickKeysOnDesktop);
|
const showTerminalQuickKeysOnDesktop = useUIStore((state) => state.showTerminalQuickKeysOnDesktop);
|
||||||
@@ -973,7 +972,7 @@ export const TerminalView: React.FC = () => {
|
|||||||
onResize={handleViewportResize}
|
onResize={handleViewportResize}
|
||||||
theme={xtermTheme}
|
theme={xtermTheme}
|
||||||
fontFamily={resolvedFontStack}
|
fontFamily={resolvedFontStack}
|
||||||
fontSize={TERMINAL_FONT_SIZE}
|
fontSize={terminalFontSize}
|
||||||
enableTouchScroll={hasTouchInput}
|
enableTouchScroll={hasTouchInput}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -989,7 +988,7 @@ export const TerminalView: React.FC = () => {
|
|||||||
onResize={handleViewportResize}
|
onResize={handleViewportResize}
|
||||||
theme={xtermTheme}
|
theme={xtermTheme}
|
||||||
fontFamily={resolvedFontStack}
|
fontFamily={resolvedFontStack}
|
||||||
fontSize={TERMINAL_FONT_SIZE}
|
fontSize={terminalFontSize}
|
||||||
enableTouchScroll={hasTouchInput}
|
enableTouchScroll={hasTouchInput}
|
||||||
/>
|
/>
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
|
|||||||
@@ -403,6 +403,7 @@ export interface SettingsPayload {
|
|||||||
gitmojiEnabled?: boolean;
|
gitmojiEnabled?: boolean;
|
||||||
toolCallExpansion?: 'collapsed' | 'activity' | 'detailed';
|
toolCallExpansion?: 'collapsed' | 'activity' | 'detailed';
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
|
terminalFontSize?: number;
|
||||||
padding?: number;
|
padding?: number;
|
||||||
cornerRadius?: number;
|
cornerRadius?: number;
|
||||||
inputBarOffset?: number;
|
inputBarOffset?: number;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type AppearanceSlice = {
|
|||||||
autoDeleteAfterDays: number;
|
autoDeleteAfterDays: number;
|
||||||
toolCallExpansion: 'collapsed' | 'activity' | 'detailed';
|
toolCallExpansion: 'collapsed' | 'activity' | 'detailed';
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
|
terminalFontSize: number;
|
||||||
padding: number;
|
padding: number;
|
||||||
cornerRadius: number;
|
cornerRadius: number;
|
||||||
inputBarOffset: number;
|
inputBarOffset: number;
|
||||||
@@ -38,6 +39,7 @@ export const startAppearanceAutoSave = (): void => {
|
|||||||
autoDeleteAfterDays: useUIStore.getState().autoDeleteAfterDays,
|
autoDeleteAfterDays: useUIStore.getState().autoDeleteAfterDays,
|
||||||
toolCallExpansion: useUIStore.getState().toolCallExpansion,
|
toolCallExpansion: useUIStore.getState().toolCallExpansion,
|
||||||
fontSize: useUIStore.getState().fontSize,
|
fontSize: useUIStore.getState().fontSize,
|
||||||
|
terminalFontSize: useUIStore.getState().terminalFontSize,
|
||||||
padding: useUIStore.getState().padding,
|
padding: useUIStore.getState().padding,
|
||||||
cornerRadius: useUIStore.getState().cornerRadius,
|
cornerRadius: useUIStore.getState().cornerRadius,
|
||||||
inputBarOffset: useUIStore.getState().inputBarOffset,
|
inputBarOffset: useUIStore.getState().inputBarOffset,
|
||||||
@@ -76,6 +78,7 @@ export const startAppearanceAutoSave = (): void => {
|
|||||||
autoDeleteAfterDays: state.autoDeleteAfterDays,
|
autoDeleteAfterDays: state.autoDeleteAfterDays,
|
||||||
toolCallExpansion: state.toolCallExpansion,
|
toolCallExpansion: state.toolCallExpansion,
|
||||||
fontSize: state.fontSize,
|
fontSize: state.fontSize,
|
||||||
|
terminalFontSize: state.terminalFontSize,
|
||||||
padding: state.padding,
|
padding: state.padding,
|
||||||
cornerRadius: state.cornerRadius,
|
cornerRadius: state.cornerRadius,
|
||||||
inputBarOffset: state.inputBarOffset,
|
inputBarOffset: state.inputBarOffset,
|
||||||
@@ -112,6 +115,9 @@ export const startAppearanceAutoSave = (): void => {
|
|||||||
if (current.fontSize !== previous.fontSize) {
|
if (current.fontSize !== previous.fontSize) {
|
||||||
diff.fontSize = current.fontSize;
|
diff.fontSize = current.fontSize;
|
||||||
}
|
}
|
||||||
|
if (current.terminalFontSize !== previous.terminalFontSize) {
|
||||||
|
diff.terminalFontSize = current.terminalFontSize;
|
||||||
|
}
|
||||||
if (current.padding !== previous.padding) {
|
if (current.padding !== previous.padding) {
|
||||||
diff.padding = current.padding;
|
diff.padding = current.padding;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export type DesktopSettings = {
|
|||||||
gitmojiEnabled?: boolean;
|
gitmojiEnabled?: boolean;
|
||||||
toolCallExpansion?: 'collapsed' | 'activity' | 'detailed';
|
toolCallExpansion?: 'collapsed' | 'activity' | 'detailed';
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
|
terminalFontSize?: number;
|
||||||
padding?: number;
|
padding?: number;
|
||||||
cornerRadius?: number;
|
cornerRadius?: number;
|
||||||
inputBarOffset?: number;
|
inputBarOffset?: number;
|
||||||
|
|||||||
@@ -228,6 +228,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
|||||||
if (typeof settings.fontSize === 'number' && Number.isFinite(settings.fontSize) && settings.fontSize !== store.fontSize) {
|
if (typeof settings.fontSize === 'number' && Number.isFinite(settings.fontSize) && settings.fontSize !== store.fontSize) {
|
||||||
store.setFontSize(settings.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) {
|
if (typeof settings.padding === 'number' && Number.isFinite(settings.padding) && settings.padding !== store.padding) {
|
||||||
store.setPadding(settings.padding);
|
store.setPadding(settings.padding);
|
||||||
}
|
}
|
||||||
@@ -376,6 +379,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
|||||||
if (typeof candidate.fontSize === 'number' && Number.isFinite(candidate.fontSize)) {
|
if (typeof candidate.fontSize === 'number' && Number.isFinite(candidate.fontSize)) {
|
||||||
result.fontSize = 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)) {
|
if (typeof candidate.padding === 'number' && Number.isFinite(candidate.padding)) {
|
||||||
result.padding = candidate.padding;
|
result.padding = candidate.padding;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ interface UIStore {
|
|||||||
|
|
||||||
toolCallExpansion: 'collapsed' | 'activity' | 'detailed';
|
toolCallExpansion: 'collapsed' | 'activity' | 'detailed';
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
|
terminalFontSize: number;
|
||||||
padding: number;
|
padding: number;
|
||||||
cornerRadius: number;
|
cornerRadius: number;
|
||||||
inputBarOffset: number;
|
inputBarOffset: number;
|
||||||
@@ -104,6 +105,7 @@ interface UIStore {
|
|||||||
setMemoryLimitActiveSession: (value: number) => void;
|
setMemoryLimitActiveSession: (value: number) => void;
|
||||||
setToolCallExpansion: (value: 'collapsed' | 'activity' | 'detailed') => void;
|
setToolCallExpansion: (value: 'collapsed' | 'activity' | 'detailed') => void;
|
||||||
setFontSize: (size: number) => void;
|
setFontSize: (size: number) => void;
|
||||||
|
setTerminalFontSize: (size: number) => void;
|
||||||
setPadding: (size: number) => void;
|
setPadding: (size: number) => void;
|
||||||
setCornerRadius: (radius: number) => void;
|
setCornerRadius: (radius: number) => void;
|
||||||
setInputBarOffset: (offset: number) => void;
|
setInputBarOffset: (offset: number) => void;
|
||||||
@@ -168,6 +170,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
memoryLimitActiveSession: 180,
|
memoryLimitActiveSession: 180,
|
||||||
toolCallExpansion: 'collapsed',
|
toolCallExpansion: 'collapsed',
|
||||||
fontSize: 100,
|
fontSize: 100,
|
||||||
|
terminalFontSize: 13,
|
||||||
padding: 100,
|
padding: 100,
|
||||||
cornerRadius: 12,
|
cornerRadius: 12,
|
||||||
inputBarOffset: 0,
|
inputBarOffset: 0,
|
||||||
@@ -403,6 +406,12 @@ export const useUIStore = create<UIStore>()(
|
|||||||
get().applyTypography();
|
get().applyTypography();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setTerminalFontSize: (size) => {
|
||||||
|
const rounded = Math.round(size);
|
||||||
|
const clamped = Math.max(9, Math.min(52, rounded));
|
||||||
|
set({ terminalFontSize: clamped });
|
||||||
|
},
|
||||||
|
|
||||||
setPadding: (size) => {
|
setPadding: (size) => {
|
||||||
// Clamp between 50% and 200%
|
// Clamp between 50% and 200%
|
||||||
const clampedSize = Math.max(50, Math.min(200, size));
|
const clampedSize = Math.max(50, Math.min(200, size));
|
||||||
@@ -680,6 +689,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
memoryLimitActiveSession: state.memoryLimitActiveSession,
|
memoryLimitActiveSession: state.memoryLimitActiveSession,
|
||||||
toolCallExpansion: state.toolCallExpansion,
|
toolCallExpansion: state.toolCallExpansion,
|
||||||
fontSize: state.fontSize,
|
fontSize: state.fontSize,
|
||||||
|
terminalFontSize: state.terminalFontSize,
|
||||||
padding: state.padding,
|
padding: state.padding,
|
||||||
cornerRadius: state.cornerRadius,
|
cornerRadius: state.cornerRadius,
|
||||||
favoriteModels: state.favoriteModels,
|
favoriteModels: state.favoriteModels,
|
||||||
|
|||||||
@@ -1034,6 +1034,9 @@ const sanitizeSettingsUpdate = (payload) => {
|
|||||||
if (typeof candidate.fontSize === 'number' && Number.isFinite(candidate.fontSize)) {
|
if (typeof candidate.fontSize === 'number' && Number.isFinite(candidate.fontSize)) {
|
||||||
result.fontSize = Math.max(50, Math.min(200, Math.round(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)) {
|
if (typeof candidate.padding === 'number' && Number.isFinite(candidate.padding)) {
|
||||||
result.padding = Math.max(50, Math.min(200, Math.round(candidate.padding)));
|
result.padding = Math.max(50, Math.min(200, Math.round(candidate.padding)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user