diff --git a/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx b/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx index 154ebcef..f1cebb10 100644 --- a/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx +++ b/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx @@ -2,11 +2,19 @@ import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Slider } from '@/components/ui/slider'; import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { SquareTerminal } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAppStore } from '@/store/app-store'; import { toast } from 'sonner'; import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes'; +import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options'; export function TerminalSection() { const { @@ -53,27 +61,32 @@ export function TerminalSection() { {/* Font Family */}
- { + setTerminalFontFamily(value); toast.info('Font family changed', { description: 'Restart terminal for changes to take effect', }); }} - className={cn( - 'w-full px-3 py-2 rounded-lg', - 'bg-accent/30 border border-border/50', - 'text-foreground text-sm', - 'focus:outline-none focus:ring-2 focus:ring-green-500/30' - )} > - {TERMINAL_FONT_OPTIONS.map((font) => ( - - ))} - + + + + + {TERMINAL_FONT_OPTIONS.map((option) => ( + + + {option.label} + + + ))} + +
{/* Default Font Size */} diff --git a/apps/ui/src/components/views/terminal-view.tsx b/apps/ui/src/components/views/terminal-view.tsx index 0cad0408..328afc21 100644 --- a/apps/ui/src/components/views/terminal-view.tsx +++ b/apps/ui/src/components/views/terminal-view.tsx @@ -25,8 +25,17 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Slider } from '@/components/ui/slider'; +import { Switch } from '@/components/ui/switch'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes'; +import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options'; import { toast } from 'sonner'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { TerminalPanel } from './terminal-view/terminal-panel'; @@ -232,6 +241,8 @@ export function TerminalView() { setTerminalDefaultRunScript, setTerminalFontFamily, setTerminalLineHeight, + setTerminalScrollbackLines, + setTerminalScreenReaderMode, updateTerminalPanelSizes, } = useAppStore(); @@ -1457,9 +1468,9 @@ export function TerminalView() { - +
-
+

Terminal Settings

Configure global terminal appearance @@ -1469,15 +1480,15 @@ export function TerminalView() { {/* Default Font Size */}

- - + + {terminalState.defaultFontSize}px
setTerminalDefaultFontSize(value)} onValueCommit={() => { @@ -1488,37 +1499,79 @@ export function TerminalView() { />
+ {/* Default Run Script */} +
+ + setTerminalDefaultRunScript(e.target.value)} + placeholder="e.g., claude" + className="h-7 text-xs" + /> +

+ Command to run when creating a new terminal +

+
+ {/* Font Family */}
- - { + setTerminalFontFamily(value); toast.info('Font family changed', { description: 'Restart terminal for changes to take effect', }); }} - className={cn( - 'w-full px-2 py-1.5 rounded-md text-sm', - 'bg-accent/50 border border-border', - 'text-foreground', - 'focus:outline-none focus:ring-2 focus:ring-ring' - )} > - {TERMINAL_FONT_OPTIONS.map((font) => ( - - ))} - + + + + + {TERMINAL_FONT_OPTIONS.map((option) => ( + + + {option.label} + + + ))} + + +
+ + {/* Scrollback */} +
+
+ + + {(terminalState.scrollbackLines / 1000).toFixed(0)}k lines + +
+ setTerminalScrollbackLines(value)} + onValueCommit={() => { + toast.info('Scrollback changed', { + description: 'Restart terminal for changes to take effect', + }); + }} + />
{/* Line Height */}
- - + + {terminalState.lineHeight.toFixed(1)}
@@ -1536,18 +1589,21 @@ export function TerminalView() { />
- {/* Default Run Script */} -
- - setTerminalDefaultRunScript(e.target.value)} - placeholder="e.g., claude, npm run dev" - className="h-8 text-sm" + {/* Screen Reader */} +
+
+ +

Enable accessibility mode

+
+ { + setTerminalScreenReaderMode(checked); + toast.info(checked ? 'Screen reader enabled' : 'Screen reader disabled', { + description: 'Restart terminal for changes to take effect', + }); + }} /> -

- Command to run when opening new terminals -

diff --git a/apps/ui/src/components/views/terminal-view/terminal-panel.tsx b/apps/ui/src/components/views/terminal-view/terminal-panel.tsx index 24a91f46..481ee6b4 100644 --- a/apps/ui/src/components/views/terminal-view/terminal-panel.tsx +++ b/apps/ui/src/components/views/terminal-view/terminal-panel.tsx @@ -30,6 +30,13 @@ import { Slider } from '@/components/ui/slider'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { useDraggable, useDroppable } from '@dnd-kit/core'; import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, type KeyboardShortcuts } from '@/store/app-store'; import { useShallow } from 'zustand/react/shallow'; @@ -38,7 +45,9 @@ import { getTerminalTheme, TERMINAL_FONT_OPTIONS, DEFAULT_TERMINAL_FONT, + getTerminalFontFamily, } from '@/config/terminal-themes'; +import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options'; import { toast } from 'sonner'; import { getElectronAPI } from '@/lib/electron'; import { getApiKey, getSessionToken, getServerUrlSync } from '@/lib/http-api-client'; @@ -567,7 +576,7 @@ export function TerminalPanel({ // Get settings from store (read at initialization time) const terminalSettings = useAppStore.getState().terminalState; const screenReaderEnabled = terminalSettings.screenReaderMode; - const terminalFontFamily = terminalSettings.fontFamily || DEFAULT_TERMINAL_FONT; + const terminalFontFamily = getTerminalFontFamily(terminalSettings.fontFamily); const terminalScrollback = terminalSettings.scrollbackLines || 5000; const terminalLineHeight = terminalSettings.lineHeight || 1.0; @@ -1269,7 +1278,7 @@ export function TerminalPanel({ useEffect(() => { if (xtermRef.current && isTerminalReady) { - xtermRef.current.options.fontFamily = fontFamily; + xtermRef.current.options.fontFamily = getTerminalFontFamily(fontFamily); fitAddonRef.current?.fit(); } }, [fontFamily, isTerminalReady]); @@ -1902,22 +1911,33 @@ export function TerminalPanel({
- { + setTerminalFontFamily(value); toast.info('Font family changed', { description: 'Restart terminal for changes to take effect', }); }} - className="w-full h-7 text-xs bg-background border border-input rounded-md px-2" > - {TERMINAL_FONT_OPTIONS.map((font) => ( - - ))} - + + + + + {TERMINAL_FONT_OPTIONS.map((option) => ( + + + {option.label} + + + ))} + +
diff --git a/apps/ui/src/config/terminal-themes.ts b/apps/ui/src/config/terminal-themes.ts index e23feba3..4573bc01 100644 --- a/apps/ui/src/config/terminal-themes.ts +++ b/apps/ui/src/config/terminal-themes.ts @@ -4,6 +4,11 @@ */ import type { ThemeMode } from '@/store/app-store'; +import { + UI_MONO_FONT_OPTIONS, + DEFAULT_FONT_VALUE, + type UIFontOption, +} from '@/config/ui-font-options'; export interface TerminalTheme { background: string; @@ -37,27 +42,44 @@ export interface TerminalTheme { /** * Terminal font options for user selection - * These are monospace fonts commonly available on different platforms + * + * Uses the same fonts as UI_MONO_FONT_OPTIONS for consistency across the app. + * All fonts listed here are bundled with the app via @fontsource packages + * or are system fonts with appropriate fallbacks. */ -export interface TerminalFontOption { - value: string; - label: string; -} -export const TERMINAL_FONT_OPTIONS: TerminalFontOption[] = [ - { value: "Menlo, Monaco, 'Courier New', monospace", label: 'Menlo / Monaco' }, - { value: "'SF Mono', Menlo, Monaco, monospace", label: 'SF Mono' }, - { value: "'JetBrains Mono', monospace", label: 'JetBrains Mono' }, - { value: "'Fira Code', monospace", label: 'Fira Code' }, - { value: "'Source Code Pro', monospace", label: 'Source Code Pro' }, - { value: "Consolas, 'Courier New', monospace", label: 'Consolas' }, - { value: "'Ubuntu Mono', monospace", label: 'Ubuntu Mono' }, -]; +// Re-export for backwards compatibility +export type TerminalFontOption = UIFontOption; /** - * Default terminal font family (first option) + * Terminal font options - reuses UI_MONO_FONT_OPTIONS with terminal-specific default + * + * The 'default' value means "use the default terminal font" (Menlo/Monaco) */ -export const DEFAULT_TERMINAL_FONT = TERMINAL_FONT_OPTIONS[0].value; +export const TERMINAL_FONT_OPTIONS: readonly UIFontOption[] = UI_MONO_FONT_OPTIONS.map((option) => { + // Replace the UI default label with terminal-specific default + if (option.value === DEFAULT_FONT_VALUE) { + return { value: option.value, label: 'Default (Menlo / Monaco)' }; + } + return option; +}); + +/** + * Default terminal font family + * Uses the DEFAULT_FONT_VALUE sentinel which maps to Menlo/Monaco + */ +export const DEFAULT_TERMINAL_FONT = DEFAULT_FONT_VALUE; + +/** + * Get the actual font family CSS value for terminal + * Converts DEFAULT_FONT_VALUE to the actual Menlo/Monaco font stack + */ +export function getTerminalFontFamily(fontValue: string | undefined): string { + if (!fontValue || fontValue === DEFAULT_FONT_VALUE) { + return "Menlo, Monaco, 'Courier New', monospace"; + } + return fontValue; +} // Dark theme (default) const darkTheme: TerminalTheme = { diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts index 5789886b..07119b85 100644 --- a/apps/ui/src/hooks/use-settings-migration.ts +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -600,6 +600,13 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void { worktreePanelCollapsed: settings.worktreePanelCollapsed ?? false, lastProjectDir: settings.lastProjectDir ?? '', recentFolders: settings.recentFolders ?? [], + // Terminal font (nested in terminalState) + ...(settings.terminalFontFamily && { + terminalState: { + ...current.terminalState, + fontFamily: settings.terminalFontFamily, + }, + }), }); // Hydrate setup wizard state from global settings (API-backed) @@ -653,6 +660,7 @@ function buildSettingsUpdateFromStore(): Record { worktreePanelCollapsed: state.worktreePanelCollapsed, lastProjectDir: state.lastProjectDir, recentFolders: state.recentFolders, + terminalFontFamily: state.terminalState.fontFamily, }; } diff --git a/apps/ui/src/hooks/use-settings-sync.ts b/apps/ui/src/hooks/use-settings-sync.ts index 763c2bb0..c9430684 100644 --- a/apps/ui/src/hooks/use-settings-sync.ts +++ b/apps/ui/src/hooks/use-settings-sync.ts @@ -35,6 +35,7 @@ const SETTINGS_FIELDS_TO_SYNC = [ 'theme', 'fontFamilySans', 'fontFamilyMono', + 'terminalFontFamily', // Maps to terminalState.fontFamily 'sidebarOpen', 'chatHistoryOpen', 'maxConcurrency', @@ -159,6 +160,9 @@ export function useSettingsSync(): SettingsSyncState { if (field === 'currentProjectId') { // Special handling: extract ID from currentProject object updates[field] = appState.currentProject?.id ?? null; + } else if (field === 'terminalFontFamily') { + // Special handling: map terminalState.fontFamily to terminalFontFamily + updates[field] = appState.terminalState.fontFamily; } else { updates[field] = appState[field as keyof typeof appState]; } @@ -260,6 +264,8 @@ export function useSettingsSync(): SettingsSyncState { for (const field of SETTINGS_FIELDS_TO_SYNC) { if (field === 'currentProjectId') { updates[field] = appState.currentProject?.id ?? null; + } else if (field === 'terminalFontFamily') { + updates[field] = appState.terminalState.fontFamily; } else { updates[field] = appState[field as keyof typeof appState]; } @@ -322,6 +328,12 @@ export function useSettingsSync(): SettingsSyncState { changed = true; break; } + } else if (field === 'terminalFontFamily') { + // Special handling: compare terminalState.fontFamily + if (newState.terminalState.fontFamily !== prevState.terminalState.fontFamily) { + changed = true; + break; + } } else { const key = field as keyof typeof newState; if (newState[key] !== prevState[key]) { @@ -403,6 +415,8 @@ export async function forceSyncSettingsToServer(): Promise { for (const field of SETTINGS_FIELDS_TO_SYNC) { if (field === 'currentProjectId') { updates[field] = appState.currentProject?.id ?? null; + } else if (field === 'terminalFontFamily') { + updates[field] = appState.terminalState.fontFamily; } else { updates[field] = appState[field as keyof typeof appState]; } @@ -505,6 +519,13 @@ export async function refreshSettingsFromServer(): Promise { worktreePanelCollapsed: serverSettings.worktreePanelCollapsed ?? false, lastProjectDir: serverSettings.lastProjectDir ?? '', recentFolders: serverSettings.recentFolders ?? [], + // Terminal font (nested in terminalState) + ...(serverSettings.terminalFontFamily && { + terminalState: { + ...currentAppState.terminalState, + fontFamily: serverSettings.terminalFontFamily, + }, + }), }); // Also refresh setup wizard state diff --git a/apps/ui/src/styles/global.css b/apps/ui/src/styles/global.css index e0159e25..a8a6e53a 100644 --- a/apps/ui/src/styles/global.css +++ b/apps/ui/src/styles/global.css @@ -880,6 +880,11 @@ background: var(--muted-foreground); } +/* Terminal padding for better readability */ +.xterm { + padding: 12px 16px; +} + /* ======================================== DEPENDENCY GRAPH STYLES Theme-aware styling for React Flow graph diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index 1ccc2026..a48504a8 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -472,6 +472,8 @@ export interface GlobalSettings { fontFamilySans?: string; /** Global Code/Mono font family (undefined = use default Geist Mono) */ fontFamilyMono?: string; + /** Terminal font family (undefined = use default Menlo/Monaco) */ + terminalFontFamily?: string; // UI State Preferences /** Whether sidebar is currently open */