feat(settings): move font/terminal/log settings to Display panel

Moved additional visual/output settings from General to Display:
- Interface font family and size
- Terminal width (columns)
- System log level and max buffer

Updated tests and docs to reflect new Display tab location.
This commit is contained in:
Pedram Amini
2026-02-02 03:04:59 -06:00
parent dbf87ca760
commit 38925cc627
3 changed files with 130 additions and 126 deletions

View File

@@ -12,8 +12,8 @@ Settings are organized into tabs:
| Tab | Contents |
|-----|----------|
| **General** | Font family and size, terminal width, log level and buffer, shell configuration, input send behavior, default toggles (history, thinking), automatic tab naming, power management, updates, privacy, usage stats, storage location |
| **Display** | Max output lines per response, document graph settings, context window warnings |
| **General** | Shell configuration, input send behavior, default toggles (history, thinking), automatic tab naming, power management, updates, privacy, usage stats, storage location |
| **Display** | Font family and size, terminal width, log level and buffer, max output lines per response, document graph settings, context window warnings |
| **Shortcuts** | Customize keyboard shortcuts (see [Keyboard Shortcuts](./keyboard-shortcuts)) |
| **Themes** | Dark, light, and vibe mode themes, custom theme builder with import/export |
| **Notifications** | OS notifications, custom command notifications, toast notification duration |

View File

@@ -3,7 +3,7 @@
*
* Tests the SettingsModal component, including:
* - Modal rendering and isOpen conditional
* - Tab navigation (general, shortcuts, theme, notifications, aicommands)
* - Tab navigation (general, display, shortcuts, theme, notifications, aicommands)
* - Tab keyboard navigation (Cmd+Shift+[ and ])
* - Layer stack integration
* - Agent loading and configuration
@@ -297,6 +297,7 @@ describe('SettingsModal', () => {
});
expect(screen.getByTitle('General')).toBeInTheDocument();
expect(screen.getByTitle('Display')).toBeInTheDocument();
expect(screen.getByTitle('Shortcuts')).toBeInTheDocument();
expect(screen.getByTitle('Themes')).toBeInTheDocument();
expect(screen.getByTitle('Notifications')).toBeInTheDocument();
@@ -310,8 +311,8 @@ describe('SettingsModal', () => {
await vi.advanceTimersByTimeAsync(50);
});
// General tab content should show the Font Size label
expect(screen.getByText('Font Size')).toBeInTheDocument();
// General tab content should show the Default Terminal Shell label
expect(screen.getByText('Default Terminal Shell')).toBeInTheDocument();
});
it('should respect initialTab prop', async () => {
@@ -383,16 +384,17 @@ describe('SettingsModal', () => {
});
// Start on general tab
expect(screen.getByText('Font Size')).toBeInTheDocument();
expect(screen.getByText('Default Terminal Shell')).toBeInTheDocument();
// Press Cmd+Shift+] to go to shortcuts
// Press Cmd+Shift+] to go to display
fireEvent.keyDown(window, { key: ']', metaKey: true, shiftKey: true });
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
});
expect(screen.getByPlaceholderText('Filter shortcuts...')).toBeInTheDocument();
// Display tab has Font Size
expect(screen.getByText('Font Size')).toBeInTheDocument();
});
it('should navigate to previous tab with Cmd+Shift+[', async () => {
@@ -405,13 +407,14 @@ describe('SettingsModal', () => {
// Start on shortcuts tab
expect(screen.getByPlaceholderText('Filter shortcuts...')).toBeInTheDocument();
// Press Cmd+Shift+[ to go back to general
// Press Cmd+Shift+[ to go back to display
fireEvent.keyDown(window, { key: '[', metaKey: true, shiftKey: true });
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
});
// Display tab has Font Size
expect(screen.getByText('Font Size')).toBeInTheDocument();
});
@@ -432,7 +435,8 @@ describe('SettingsModal', () => {
await vi.advanceTimersByTimeAsync(100);
});
expect(screen.getByText('Font Size')).toBeInTheDocument();
// General tab has Default Terminal Shell
expect(screen.getByText('Default Terminal Shell')).toBeInTheDocument();
});
it('should wrap around when navigating before first tab', async () => {
@@ -443,9 +447,9 @@ describe('SettingsModal', () => {
});
// Start on general tab (first tab)
expect(screen.getByText('Font Size')).toBeInTheDocument();
expect(screen.getByText('Default Terminal Shell')).toBeInTheDocument();
// Press Cmd+Shift+[ to wrap to SSH (now the last tab)
// Press Cmd+Shift+[ to wrap to SSH (last tab)
fireEvent.keyDown(window, { key: '[', metaKey: true, shiftKey: true });
await act(async () => {
@@ -475,9 +479,9 @@ describe('SettingsModal', () => {
});
});
describe('General tab - Font settings', () => {
describe('Display tab - Font settings', () => {
it('should show font loading message initially', async () => {
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(50);
@@ -489,7 +493,7 @@ describe('SettingsModal', () => {
it('should call setFontFamily when font is changed', async () => {
const setFontFamily = vi.fn();
render(<SettingsModal {...createDefaultProps({ setFontFamily })} />);
render(<SettingsModal {...createDefaultProps({ setFontFamily, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -504,7 +508,7 @@ describe('SettingsModal', () => {
});
it('should load fonts when font select is focused', async () => {
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -523,10 +527,10 @@ describe('SettingsModal', () => {
});
});
describe('General tab - Font size buttons', () => {
describe('Display tab - Font size buttons', () => {
it('should call setFontSize with 12 when Small is clicked', async () => {
const setFontSize = vi.fn();
render(<SettingsModal {...createDefaultProps({ setFontSize })} />);
render(<SettingsModal {...createDefaultProps({ setFontSize, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -538,7 +542,7 @@ describe('SettingsModal', () => {
it('should call setFontSize with 14 when Medium is clicked', async () => {
const setFontSize = vi.fn();
render(<SettingsModal {...createDefaultProps({ setFontSize })} />);
render(<SettingsModal {...createDefaultProps({ setFontSize, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -550,7 +554,7 @@ describe('SettingsModal', () => {
it('should call setFontSize with 16 when Large is clicked', async () => {
const setFontSize = vi.fn();
render(<SettingsModal {...createDefaultProps({ setFontSize })} />);
render(<SettingsModal {...createDefaultProps({ setFontSize, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -562,7 +566,7 @@ describe('SettingsModal', () => {
it('should call setFontSize with 18 when X-Large is clicked', async () => {
const setFontSize = vi.fn();
render(<SettingsModal {...createDefaultProps({ setFontSize })} />);
render(<SettingsModal {...createDefaultProps({ setFontSize, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -573,7 +577,7 @@ describe('SettingsModal', () => {
});
it('should highlight selected font size', async () => {
render(<SettingsModal {...createDefaultProps({ fontSize: 14 })} />);
render(<SettingsModal {...createDefaultProps({ fontSize: 14, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -584,10 +588,10 @@ describe('SettingsModal', () => {
});
});
describe('General tab - Terminal width buttons', () => {
describe('Display tab - Terminal width buttons', () => {
it('should call setTerminalWidth with 80', async () => {
const setTerminalWidth = vi.fn();
render(<SettingsModal {...createDefaultProps({ setTerminalWidth })} />);
render(<SettingsModal {...createDefaultProps({ setTerminalWidth, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -599,7 +603,7 @@ describe('SettingsModal', () => {
it('should call setTerminalWidth with 100', async () => {
const setTerminalWidth = vi.fn();
render(<SettingsModal {...createDefaultProps({ setTerminalWidth })} />);
render(<SettingsModal {...createDefaultProps({ setTerminalWidth, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -613,10 +617,10 @@ describe('SettingsModal', () => {
});
});
describe('General tab - Log level buttons', () => {
describe('Display tab - Log level buttons', () => {
it('should call setLogLevel with debug', async () => {
const setLogLevel = vi.fn();
render(<SettingsModal {...createDefaultProps({ setLogLevel })} />);
render(<SettingsModal {...createDefaultProps({ setLogLevel, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -628,7 +632,7 @@ describe('SettingsModal', () => {
it('should call setLogLevel with info', async () => {
const setLogLevel = vi.fn();
render(<SettingsModal {...createDefaultProps({ setLogLevel })} />);
render(<SettingsModal {...createDefaultProps({ setLogLevel, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -640,7 +644,7 @@ describe('SettingsModal', () => {
it('should call setLogLevel with warn', async () => {
const setLogLevel = vi.fn();
render(<SettingsModal {...createDefaultProps({ setLogLevel })} />);
render(<SettingsModal {...createDefaultProps({ setLogLevel, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -652,7 +656,7 @@ describe('SettingsModal', () => {
it('should call setLogLevel with error', async () => {
const setLogLevel = vi.fn();
render(<SettingsModal {...createDefaultProps({ setLogLevel })} />);
render(<SettingsModal {...createDefaultProps({ setLogLevel, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -663,10 +667,10 @@ describe('SettingsModal', () => {
});
});
describe('General tab - Max log buffer buttons', () => {
describe('Display tab - Max log buffer buttons', () => {
it('should call setMaxLogBuffer with various values', async () => {
const setMaxLogBuffer = vi.fn();
render(<SettingsModal {...createDefaultProps({ setMaxLogBuffer })} />);
render(<SettingsModal {...createDefaultProps({ setMaxLogBuffer, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -686,10 +690,10 @@ describe('SettingsModal', () => {
});
});
describe('General tab - Max output lines buttons', () => {
describe('Display tab - Max output lines buttons', () => {
it('should call setMaxOutputLines with various values', async () => {
const setMaxOutputLines = vi.fn();
render(<SettingsModal {...createDefaultProps({ setMaxOutputLines })} />);
render(<SettingsModal {...createDefaultProps({ setMaxOutputLines, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1290,7 +1294,7 @@ describe('SettingsModal', () => {
describe('custom fonts', () => {
it('should add custom font when input is submitted', async () => {
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1308,7 +1312,7 @@ describe('SettingsModal', () => {
});
it('should add custom font on Enter key', async () => {
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1326,7 +1330,7 @@ describe('SettingsModal', () => {
});
it('should not add empty custom font', async () => {
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1353,7 +1357,7 @@ describe('SettingsModal', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1817,7 +1821,7 @@ describe('SettingsModal', () => {
// Preload custom fonts
vi.mocked(window.maestro.settings.get).mockResolvedValue(['MyCustomFont', 'AnotherFont']);
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1851,7 +1855,7 @@ describe('SettingsModal', () => {
describe('Terminal width 120 and 160 buttons', () => {
it('should call setTerminalWidth with 120', async () => {
const setTerminalWidth = vi.fn();
render(<SettingsModal {...createDefaultProps({ setTerminalWidth })} />);
render(<SettingsModal {...createDefaultProps({ setTerminalWidth, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1863,7 +1867,7 @@ describe('SettingsModal', () => {
it('should call setTerminalWidth with 160', async () => {
const setTerminalWidth = vi.fn();
render(<SettingsModal {...createDefaultProps({ setTerminalWidth })} />);
render(<SettingsModal {...createDefaultProps({ setTerminalWidth, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1877,7 +1881,7 @@ describe('SettingsModal', () => {
describe('Max output lines 100 button', () => {
it('should call setMaxOutputLines with 100', async () => {
const setMaxOutputLines = vi.fn();
render(<SettingsModal {...createDefaultProps({ setMaxOutputLines })} />);
render(<SettingsModal {...createDefaultProps({ setMaxOutputLines, initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);
@@ -1895,7 +1899,7 @@ describe('SettingsModal', () => {
it('should check font availability using normalized names', async () => {
(window.maestro as any).fonts.detect.mockResolvedValue(['JetBrains Mono', 'Fira Code']);
render(<SettingsModal {...createDefaultProps()} />);
render(<SettingsModal {...createDefaultProps({ initialTab: 'display' })} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(100);

View File

@@ -951,89 +951,6 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
<div className="flex-1 p-6 overflow-y-auto scrollbar-thin">
{activeTab === 'general' && (
<div className="space-y-5">
{/* Font Family */}
<FontConfigurationPanel
fontFamily={props.fontFamily}
setFontFamily={props.setFontFamily}
systemFonts={systemFonts}
fontsLoaded={fontsLoaded}
fontLoading={fontLoading}
customFonts={customFonts}
onAddCustomFont={addCustomFont}
onRemoveCustomFont={removeCustomFont}
onFontInteraction={handleFontInteraction}
theme={theme}
/>
{/* Font Size */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
Font Size
</label>
<ToggleButtonGroup
options={[
{ value: 12, label: 'Small' },
{ value: 14, label: 'Medium' },
{ value: 16, label: 'Large' },
{ value: 18, label: 'X-Large' },
]}
value={props.fontSize}
onChange={props.setFontSize}
theme={theme}
/>
</div>
{/* Terminal Width */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
Terminal Width (Columns)
</label>
<ToggleButtonGroup
options={[80, 100, 120, 160]}
value={props.terminalWidth}
onChange={props.setTerminalWidth}
theme={theme}
/>
</div>
{/* Log Level */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
System Log Level
</label>
<ToggleButtonGroup
options={[
{ value: 'debug', label: 'Debug', activeColor: '#6366f1' },
{ value: 'info', label: 'Info', activeColor: '#3b82f6' },
{ value: 'warn', label: 'Warn', activeColor: '#f59e0b' },
{ value: 'error', label: 'Error', activeColor: '#ef4444' },
]}
value={props.logLevel}
onChange={props.setLogLevel}
theme={theme}
/>
<p className="text-xs opacity-50 mt-2">
Higher levels show fewer logs. Debug shows all logs, Error shows only errors.
</p>
</div>
{/* Max Log Buffer */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
Maximum Log Buffer
</label>
<ToggleButtonGroup
options={[1000, 5000, 10000, 25000]}
value={props.maxLogBuffer}
onChange={props.setMaxLogBuffer}
theme={theme}
/>
<p className="text-xs opacity-50 mt-2">
Maximum number of log messages to keep in memory. Older logs are automatically
removed.
</p>
</div>
{/* Default Shell */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-1 flex items-center gap-2">
@@ -2044,6 +1961,89 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
{activeTab === 'display' && (
<div className="space-y-5">
{/* Font Family */}
<FontConfigurationPanel
fontFamily={props.fontFamily}
setFontFamily={props.setFontFamily}
systemFonts={systemFonts}
fontsLoaded={fontsLoaded}
fontLoading={fontLoading}
customFonts={customFonts}
onAddCustomFont={addCustomFont}
onRemoveCustomFont={removeCustomFont}
onFontInteraction={handleFontInteraction}
theme={theme}
/>
{/* Font Size */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
Font Size
</label>
<ToggleButtonGroup
options={[
{ value: 12, label: 'Small' },
{ value: 14, label: 'Medium' },
{ value: 16, label: 'Large' },
{ value: 18, label: 'X-Large' },
]}
value={props.fontSize}
onChange={props.setFontSize}
theme={theme}
/>
</div>
{/* Terminal Width */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
Terminal Width (Columns)
</label>
<ToggleButtonGroup
options={[80, 100, 120, 160]}
value={props.terminalWidth}
onChange={props.setTerminalWidth}
theme={theme}
/>
</div>
{/* Log Level */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
System Log Level
</label>
<ToggleButtonGroup
options={[
{ value: 'debug', label: 'Debug', activeColor: '#6366f1' },
{ value: 'info', label: 'Info', activeColor: '#3b82f6' },
{ value: 'warn', label: 'Warn', activeColor: '#f59e0b' },
{ value: 'error', label: 'Error', activeColor: '#ef4444' },
]}
value={props.logLevel}
onChange={props.setLogLevel}
theme={theme}
/>
<p className="text-xs opacity-50 mt-2">
Higher levels show fewer logs. Debug shows all logs, Error shows only errors.
</p>
</div>
{/* Max Log Buffer */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">
Maximum Log Buffer
</label>
<ToggleButtonGroup
options={[1000, 5000, 10000, 25000]}
value={props.maxLogBuffer}
onChange={props.setMaxLogBuffer}
theme={theme}
/>
<p className="text-xs opacity-50 mt-2">
Maximum number of log messages to keep in memory. Older logs are automatically
removed.
</p>
</div>
{/* Max Output Lines */}
<div>
<label className="block text-xs font-bold opacity-70 uppercase mb-2">