diff --git a/src/renderer/src/components/settings/AppearancePane.tsx b/src/renderer/src/components/settings/AppearancePane.tsx index 07b816df1..cb9efa941 100644 --- a/src/renderer/src/components/settings/AppearancePane.tsx +++ b/src/renderer/src/components/settings/AppearancePane.tsx @@ -29,6 +29,9 @@ import { TYPOGRAPHY_ENTRIES, ZOOM_ENTRIES } from './appearance-search' +import { TERMINAL_APPEARANCE_SEARCH_ENTRIES } from './terminal-search' +import { TerminalAppearanceSection } from './TerminalAppearanceSection' +import type { UseGhosttyImportReturn } from './useGhosttyImport' export { APPEARANCE_PANE_SEARCH_ENTRIES } type AppearancePaneProps = { @@ -36,6 +39,9 @@ type AppearancePaneProps = { updateSettings: (updates: Partial) => void applyTheme: (theme: 'system' | 'dark' | 'light') => void fontSuggestions: string[] + terminalFontSuggestions: string[] + systemPrefersDark: boolean + ghostty: UseGhosttyImportReturn } function ShortcutHintList({ combos }: { combos: string[][] }): React.JSX.Element { @@ -61,7 +67,10 @@ export function AppearancePane({ settings, updateSettings, applyTheme, - fontSuggestions + fontSuggestions, + terminalFontSuggestions, + systemPrefersDark, + ghostty }: AppearancePaneProps): React.JSX.Element { const searchQuery = useAppStore((state) => state.settingsSearchQuery) const zoomInKeyCombos = useShortcutKeyCombos('zoom.in') @@ -149,6 +158,16 @@ export function AppearancePane({ ) : null} ) : null, + matchesSettingsSearch(searchQuery, TERMINAL_APPEARANCE_SEARCH_ENTRIES) ? ( + + ) : null, matchesSettingsSearch(searchQuery, LAYOUT_ENTRIES) ? (
diff --git a/src/renderer/src/components/settings/Settings.tsx b/src/renderer/src/components/settings/Settings.tsx index a9ee1bb4d..b365b38e0 100644 --- a/src/renderer/src/components/settings/Settings.tsx +++ b/src/renderer/src/components/settings/Settings.tsx @@ -24,9 +24,7 @@ import { ShortcutsPane } from './ShortcutsPane' import { TerminalPane } from './TerminalPane' import { FloatingWorkspacePane } from './FloatingWorkspacePane' import { useGhosttyImport } from './useGhosttyImport' -import { Button } from '../ui/button' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' -import ghosttyIcon from '../../../../../resources/ghostty.svg' import { RepositoryPane } from './RepositoryPane' import { GitPane } from './GitPane' import { CommitMessageAiPane } from './CommitMessageAiPane' @@ -80,7 +78,7 @@ const SETTINGS_NAV_GROUPS = [ { id: 'workflows', title: 'Workflows' }, { id: 'interface', title: 'Interface' }, { id: 'remote', title: 'Remote Access' }, - { id: 'safety', title: 'Safety' }, + { id: 'security', title: 'Privacy & Security' }, { id: 'experimental', title: 'Experimental' } ] as const @@ -194,9 +192,8 @@ function Settings(): React.JSX.Element { // reveals controls that the renderer will intentionally hide. const [scrollbackMode, setScrollbackMode] = useState<'preset' | 'custom'>('preset') const [prevScrollbackBytes, setPrevScrollbackBytes] = useState(settings?.terminalScrollbackBytes) - // Why: lifted out of TerminalPane so the Terminal section header can render - // the import trigger as a headerAction. The modal itself still lives inside - // TerminalPane, driven by this shared state. + // Why: Appearance owns terminal visual controls, but the Ghostty import flow + // still needs Settings-level state so the modal survives section remounts. const ghostty = useGhosttyImport(updateSettings, settings) const [fontSuggestions, setFontSuggestions] = useState( Array.from(new Set([DEFAULT_APP_FONT_FAMILY, ...getFallbackTerminalFonts()])) @@ -951,45 +948,18 @@ function Settings(): React.JSX.Element { ) : null} - - {isSectionMounted('floating-workspace') ? ( - - ) : null} - - void ghostty.handleClick()} - > - - Import from Ghostty - - } > {isSectionMounted('terminal') ? ( font !== DEFAULT_APP_FONT_FAMILY - )} scrollbackMode={scrollbackMode} setScrollbackMode={setScrollbackMode} - ghostty={ghostty} wslAvailable={windowsTerminalCapabilities.wslAvailable} wslDistros={windowsTerminalCapabilities.wslDistros} wslCapabilitiesLoading={windowsTerminalCapabilities.isLoading} @@ -1031,10 +1001,21 @@ function Settings(): React.JSX.Element { ) : null} + + {isSectionMounted('floating-workspace') ? ( + + ) : null} + + {isSectionMounted('appearance') ? ( @@ -1043,6 +1024,11 @@ function Settings(): React.JSX.Element { updateSettings={updateSettings} applyTheme={applyTheme} fontSuggestions={fontSuggestions} + terminalFontSuggestions={fontSuggestions.filter( + (font) => font !== DEFAULT_APP_FONT_FAMILY + )} + systemPrefersDark={systemPrefersDark} + ghostty={ghostty} /> ) : null} diff --git a/src/renderer/src/components/settings/TerminalPane.ghostty.test.ts b/src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts similarity index 91% rename from src/renderer/src/components/settings/TerminalPane.ghostty.test.ts rename to src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts index ba86793f6..62e486b02 100644 --- a/src/renderer/src/components/settings/TerminalPane.ghostty.test.ts +++ b/src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts @@ -154,7 +154,7 @@ const ghosttyMock = { handleOpenChange: vi.fn() } -import { TerminalPane } from './TerminalPane' +import { TerminalAppearanceSection } from './TerminalAppearanceSection' type ReactElementLike = { type: unknown @@ -234,40 +234,36 @@ function findGhosttyImportModal(node: unknown): ReactElementLike | null { return null } -describe('TerminalPane ghostty import wiring', () => { +describe('TerminalAppearanceSection ghostty import wiring', () => { beforeEach(() => { mockStateValues.length = 0 resetMockState() vi.clearAllMocks() }) - // Why: the Ghostty import trigger button lives on the section header in - // Settings.tsx (headerAction) — not inside TerminalPane. Keep this test - // around so a regression that moves the button back into the pane fails. - it('does not render an Import from Ghostty button inside the pane', () => { - const element = TerminalPane({ + it('renders the Import from Ghostty button with terminal appearance controls', () => { + const element = TerminalAppearanceSection({ settings: {} as never, updateSettings: () => {}, systemPrefersDark: true, terminalFontSuggestions: [], - scrollbackMode: 'preset', - setScrollbackMode: () => {}, ghostty: ghosttyMock }) const buttons = findButtons(element) const importButton = buttons.find((b) => b.text === 'Import from Ghostty') - expect(importButton).toBeUndefined() + expect(importButton).toBeDefined() + + importButton?.onClick?.() + expect(ghosttyMock.handleClick).toHaveBeenCalled() }) it('passes hook state to GhosttyImportModal', () => { - const element = TerminalPane({ + const element = TerminalAppearanceSection({ settings: {} as never, updateSettings: () => {}, systemPrefersDark: true, terminalFontSuggestions: [], - scrollbackMode: 'preset', - setScrollbackMode: () => {}, ghostty: ghosttyMock }) diff --git a/src/renderer/src/components/settings/TerminalAppearanceSection.tsx b/src/renderer/src/components/settings/TerminalAppearanceSection.tsx new file mode 100644 index 000000000..e52d270d2 --- /dev/null +++ b/src/renderer/src/components/settings/TerminalAppearanceSection.tsx @@ -0,0 +1,440 @@ +/* eslint-disable max-lines -- Why: terminal visual controls stay together under Appearance so + search, previews, and the Ghostty import flow move as one user-facing surface. */ +import { useState } from 'react' +import type { GlobalSettings } from '../../../../shared/types' +import { + DEFAULT_TERMINAL_FONT_WEIGHT, + TERMINAL_FONT_WEIGHT_MAX, + TERMINAL_FONT_WEIGHT_MIN, + TERMINAL_FONT_WEIGHT_STEP, + normalizeTerminalFontWeight +} from '../../../../shared/terminal-fonts' +import { + fontFamilyHasKnownLigatures, + resolveTerminalLigaturesEnabled +} from '../../../../shared/terminal-ligatures' +import { Minus, Plus } from 'lucide-react' +import { Button } from '../ui/button' +import { Input } from '../ui/input' +import { + FontAutocomplete, + NumberField, + SettingsRow, + SettingsSegmentedControl, + SettingsSubsectionHeader, + SettingsSwitchRow +} from './SettingsFormControls' +import { SearchableSetting } from './SearchableSetting' +import { matchesSettingsSearch } from './settings-search' +import { useAppStore } from '../../store' +import { clampNumber, resolvePaneStyleOptions } from '@/lib/terminal-theme' +import { + TERMINAL_CURSOR_SEARCH_ENTRIES, + TERMINAL_DARK_THEME_SEARCH_ENTRIES, + TERMINAL_GHOSTTY_IMPORT_SEARCH_ENTRIES, + TERMINAL_LIGHT_THEME_SEARCH_ENTRIES, + TERMINAL_PANE_APPEARANCE_SEARCH_ENTRIES, + TERMINAL_TYPOGRAPHY_SEARCH_ENTRIES, + TERMINAL_WINDOW_SEARCH_ENTRIES +} from './terminal-search' +import { DarkTerminalThemeSection, LightTerminalThemeSection } from './TerminalThemeSections' +import { TerminalWindowSection } from './TerminalWindowSection' +import { TerminalSettingsPreview } from './TerminalSettingsPreview' +import { GhosttyImportModal } from './GhosttyImportModal' +import type { UseGhosttyImportReturn } from './useGhosttyImport' +import ghosttyIcon from '../../../../../resources/ghostty.svg' + +type TerminalAppearanceSectionProps = { + settings: GlobalSettings + updateSettings: (updates: Partial) => void + systemPrefersDark: boolean + terminalFontSuggestions: string[] + ghostty: UseGhosttyImportReturn +} + +export function TerminalAppearanceSection({ + settings, + updateSettings, + systemPrefersDark, + terminalFontSuggestions, + ghostty +}: TerminalAppearanceSectionProps): React.JSX.Element { + const searchQuery = useAppStore((state) => state.settingsSearchQuery) + const [themeSearchDark, setThemeSearchDark] = useState('') + const [themeSearchLight, setThemeSearchLight] = useState('') + // Why: hover preview lets the font picker update the sample without committing a setting. + const [previewFontFamily, setPreviewFontFamily] = useState(null) + const paneStyleOptions = resolvePaneStyleOptions(settings) + + const visibleSections = [ + matchesSettingsSearch(searchQuery, TERMINAL_GHOSTTY_IMPORT_SEARCH_ENTRIES) || + matchesSettingsSearch(searchQuery, TERMINAL_TYPOGRAPHY_SEARCH_ENTRIES) ? ( +
+
+
+ + +
+ +
+ + + + { + const value = parseInt(e.target.value, 10) + if (!Number.isNaN(value) && value >= 10 && value <= 24) { + updateSettings({ terminalFontSize: value }) + } + }} + className="w-14 text-center tabular-nums" + /> + + px +
+ } + /> + + + + updateSettings({ terminalFontFamily: value })} + onPreviewFontFamily={setPreviewFontFamily} + /> + } + /> + + + + + updateSettings({ + terminalFontWeight: normalizeTerminalFontWeight(value) + }) + } + /> + + + + + updateSettings({ + terminalLineHeight: clampNumber(value, 1, 3) + }) + } + /> + + + + updateSettings({ terminalLigatures: option })} + options={[ + { value: 'auto', label: 'Auto' }, + { value: 'on', label: 'On' }, + { value: 'off', label: 'Off' } + ]} + /> + } + /> + {/* Why: surface the resolved state explicitly so the "Auto" label + isn't ambiguous when a user is staring at it. */} +

+ Ligatures are currently{' '} + {resolveTerminalLigaturesEnabled( + settings.terminalLigatures, + settings.terminalFontFamily + ) + ? 'enabled' + : 'disabled'} + . +

+
+
+ + +
+ ) : null, + matchesSettingsSearch(searchQuery, TERMINAL_CURSOR_SEARCH_ENTRIES) ? ( +
+ + +
+ + updateSettings({ terminalCursorStyle: option })} + options={[ + { value: 'bar', label: 'Bar' }, + { value: 'block', label: 'Block' }, + { value: 'underline', label: 'Underline' } + ]} + /> + } + /> + + + + + updateSettings({ terminalCursorBlink: !settings.terminalCursorBlink }) + } + /> + + + + + updateSettings({ + terminalCursorOpacity: clampNumber(value, 0, 1) + }) + } + /> + +
+
+ ) : null, + matchesSettingsSearch(searchQuery, TERMINAL_PANE_APPEARANCE_SEARCH_ENTRIES) ? ( +
+ + +
+ + + updateSettings({ + terminalInactivePaneOpacity: clampNumber(value, 0, 1) + }) + } + /> + + + + updateSettings({ + terminalDividerThicknessPx: clampNumber(value, 1, 32) + }) + } + /> + +
+
+ ) : null, + matchesSettingsSearch(searchQuery, TERMINAL_WINDOW_SEARCH_ENTRIES) ? ( + + ) : null, + matchesSettingsSearch(searchQuery, TERMINAL_DARK_THEME_SEARCH_ENTRIES) ? ( + + ) : null, + matchesSettingsSearch(searchQuery, TERMINAL_LIGHT_THEME_SEARCH_ENTRIES) ? ( + + ) : null + ].filter(Boolean) + + return ( +
+ {visibleSections.map((section, index) => ( +
+ {index > 0 ?
: null} + {section} +
+ ))} + +
+ ) +} diff --git a/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts b/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts index 2e9caa443..95d78b096 100644 --- a/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts +++ b/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts @@ -149,17 +149,6 @@ vi.mock('@/lib/terminal-theme', () => ({ resolvePaneStyleOptions: () => ({ inactivePaneOpacity: 0.8, dividerThicknessPx: 1 }) })) -const ghosttyMock = { - open: false, - preview: null, - loading: false, - applied: false, - applyError: null, - handleClick: vi.fn(), - handleApply: vi.fn(), - handleOpenChange: vi.fn() -} - import { TerminalPane } from './TerminalPane' type ReactElementLike = { @@ -239,11 +228,8 @@ describe('TerminalPane PowerShell version setting', () => { terminalWordSeparator: '' } as never, updateSettings: () => {}, - systemPrefersDark: true, - terminalFontSuggestions: [], scrollbackMode: 'preset', setScrollbackMode: () => {}, - ghostty: ghosttyMock, wslAvailable: false, pwshAvailable: false, gitBashAvailable: false @@ -264,11 +250,8 @@ describe('TerminalPane PowerShell version setting', () => { terminalWordSeparator: '' } as never, updateSettings: () => {}, - systemPrefersDark: true, - terminalFontSuggestions: [], scrollbackMode: 'preset', setScrollbackMode: () => {}, - ghostty: ghosttyMock, wslAvailable: true, wslDistros: ['Ubuntu'], pwshAvailable: false, @@ -287,11 +270,8 @@ describe('TerminalPane PowerShell version setting', () => { terminalWordSeparator: '' } as never, updateSettings: () => {}, - systemPrefersDark: true, - terminalFontSuggestions: [], scrollbackMode: 'preset', setScrollbackMode: () => {}, - ghostty: ghosttyMock, wslAvailable: false, pwshAvailable: false, gitBashAvailable: false @@ -310,11 +290,8 @@ describe('TerminalPane PowerShell version setting', () => { terminalWordSeparator: '' } as never, updateSettings: () => {}, - systemPrefersDark: true, - terminalFontSuggestions: [], scrollbackMode: 'preset', setScrollbackMode: () => {}, - ghostty: ghosttyMock, wslAvailable: true, wslDistros: ['Ubuntu', 'Debian'], pwshAvailable: false, @@ -337,11 +314,8 @@ describe('TerminalPane PowerShell version setting', () => { terminalWordSeparator: '' } as never, updateSettings: () => {}, - systemPrefersDark: true, - terminalFontSuggestions: [], scrollbackMode: 'preset', setScrollbackMode: () => {}, - ghostty: ghosttyMock, wslAvailable: false, pwshAvailable: false, gitBashAvailable: true @@ -359,11 +333,8 @@ describe('TerminalPane PowerShell version setting', () => { terminalWordSeparator: '' } as never, updateSettings: () => {}, - systemPrefersDark: true, - terminalFontSuggestions: [], scrollbackMode: 'preset', setScrollbackMode: () => {}, - ghostty: ghosttyMock, wslAvailable: false, pwshAvailable: false, gitBashAvailable: false diff --git a/src/renderer/src/components/settings/TerminalPane.tsx b/src/renderer/src/components/settings/TerminalPane.tsx index 8d71e0708..94ebd1243 100644 --- a/src/renderer/src/components/settings/TerminalPane.tsx +++ b/src/renderer/src/components/settings/TerminalPane.tsx @@ -1,29 +1,12 @@ -/* eslint-disable max-lines -- Why: TerminalPane is the single owner of all terminal settings UI; - splitting individual settings into separate files would scatter related controls without a - meaningful abstraction boundary. Mirrors the same decision made for GeneralPane.tsx. */ -import { useState } from 'react' +/* eslint-disable max-lines -- Why: TerminalPane keeps terminal workflow, runtime, and recovery + settings together so search shows one focused terminal behavior surface. */ import type { GlobalSettings, SetupScriptLaunchMode } from '../../../../shared/types' -import { - DEFAULT_TERMINAL_FONT_WEIGHT, - TERMINAL_FONT_WEIGHT_MAX, - TERMINAL_FONT_WEIGHT_MIN, - TERMINAL_FONT_WEIGHT_STEP, - normalizeTerminalFontWeight -} from '../../../../shared/terminal-fonts' -import { - fontFamilyHasKnownLigatures, - resolveTerminalLigaturesEnabled -} from '../../../../shared/terminal-ligatures' -import { Button } from '../ui/button' import { Input } from '../ui/input' import { Separator } from '../ui/separator' import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' -import { Minus, Plus } from 'lucide-react' -import { clampNumber, resolvePaneStyleOptions } from '@/lib/terminal-theme' +import { clampNumber } from '@/lib/terminal-theme' import { - FontAutocomplete, - NumberField, SettingsRow, SettingsSegmentedControl, SettingsSubsectionHeader, @@ -37,16 +20,11 @@ import { isMacUserAgent, isWindowsUserAgent } from '@/components/terminal-pane/p import { MANAGE_SESSIONS_SEARCH_ENTRIES, TERMINAL_ADVANCED_SEARCH_ENTRIES, - TERMINAL_CURSOR_SEARCH_ENTRIES, - TERMINAL_DARK_THEME_SEARCH_ENTRIES, - TERMINAL_LIGHT_THEME_SEARCH_ENTRIES, TERMINAL_MAC_OPTION_SEARCH_ENTRIES, TERMINAL_MAC_YEN_SEARCH_ENTRIES, - TERMINAL_PANE_STYLE_SEARCH_ENTRIES, + TERMINAL_PANE_INTERACTION_SEARCH_ENTRIES, TERMINAL_RENDERING_SEARCH_ENTRIES, - TERMINAL_SETUP_SCRIPT_SEARCH_ENTRIES, - TERMINAL_TYPOGRAPHY_SEARCH_ENTRIES, - TERMINAL_WINDOW_SEARCH_ENTRIES + TERMINAL_SETUP_SCRIPT_SEARCH_ENTRIES } from './terminal-search' import { TERMINAL_RIGHT_CLICK_TO_PASTE_SEARCH_ENTRY, @@ -54,26 +32,15 @@ import { TERMINAL_WINDOWS_SHELL_SEARCH_ENTRY } from './terminal-windows-search' import { useDetectedOptionAsAlt } from '@/lib/keyboard-layout/use-effective-mac-option-as-alt' -import { DarkTerminalThemeSection, LightTerminalThemeSection } from './TerminalThemeSections' -import { TerminalWindowSection } from './TerminalWindowSection' -import { GhosttyImportModal } from './GhosttyImportModal' -import type { UseGhosttyImportReturn } from './useGhosttyImport' import { ManageSessionsSection } from './ManageSessionsSection' -import { TerminalSettingsPreview } from './TerminalSettingsPreview' import { OSC52_CLIPBOARD_SETTING_ID } from '../terminal-pane/osc52-clipboard-setting-anchor' import { WINDOWS_GIT_BASH_SHELL } from '../../../../shared/windows-terminal-shell' type TerminalPaneProps = { settings: GlobalSettings updateSettings: (updates: Partial) => void - systemPrefersDark: boolean - terminalFontSuggestions: string[] scrollbackMode: 'preset' | 'custom' setScrollbackMode: (mode: 'preset' | 'custom') => void - /** Ghostty import modal state + handlers. Lifted to the Settings shell so - * the section header can render the trigger button as a headerAction - * instead of taking its own row inside the settings list. */ - ghostty: UseGhosttyImportReturn /** Whether WSL is installed on this Windows machine. */ wslAvailable?: boolean /** Installed WSL distro names, used to choose the default WSL terminal target. */ @@ -89,11 +56,8 @@ type TerminalPaneProps = { export function TerminalPane({ settings, updateSettings, - systemPrefersDark, - terminalFontSuggestions, scrollbackMode, setScrollbackMode, - ghostty, wslAvailable, wslDistros = [], wslCapabilitiesLoading = false, @@ -103,12 +67,6 @@ export function TerminalPane({ const searchQuery = useAppStore((state) => state.settingsSearchQuery) const isWindows = isWindowsUserAgent() const isMac = isMacUserAgent() - const [themeSearchDark, setThemeSearchDark] = useState('') - const [themeSearchLight, setThemeSearchLight] = useState('') - // Why: hover preview lets the font picker update the sample without committing a setting. - const [previewFontFamily, setPreviewFontFamily] = useState(null) - - const paneStyleOptions = resolvePaneStyleOptions(settings) const detectedLayout = useDetectedOptionAsAlt() const detectedLayoutLabel = detectedLayout === 'us' @@ -225,196 +183,6 @@ export function TerminalPane({
) : null, - matchesSettingsSearch(searchQuery, TERMINAL_TYPOGRAPHY_SEARCH_ENTRIES) ? ( -
-
- - -
- - - - { - const value = parseInt(e.target.value, 10) - if (!Number.isNaN(value) && value >= 10 && value <= 24) { - updateSettings({ terminalFontSize: value }) - } - }} - className="w-14 text-center tabular-nums" - /> - - px -
- } - /> - - - - updateSettings({ terminalFontFamily: value })} - onPreviewFontFamily={setPreviewFontFamily} - /> - } - /> - - - - - updateSettings({ - terminalFontWeight: normalizeTerminalFontWeight(value) - }) - } - /> - - - - - updateSettings({ - terminalLineHeight: clampNumber(value, 1, 3) - }) - } - /> - - - - updateSettings({ terminalLigatures: option })} - options={[ - { value: 'auto', label: 'Auto' }, - { value: 'on', label: 'On' }, - { value: 'off', label: 'Off' } - ]} - /> - } - /> - {/* Why: surface the resolved state explicitly so the "Auto" label - isn't ambiguous when a user is staring at it. */} -

- Ligatures are currently{' '} - {resolveTerminalLigaturesEnabled( - settings.terminalLigatures, - settings.terminalFontFamily - ) - ? 'enabled' - : 'disabled'} - . -

-
-
- - -
- ) : null, matchesSettingsSearch(searchQuery, TERMINAL_RENDERING_SEARCH_ENTRIES) ? (
) : null, - matchesSettingsSearch(searchQuery, TERMINAL_CURSOR_SEARCH_ENTRIES) ? ( -
- - -
- - updateSettings({ terminalCursorStyle: option })} - options={[ - { value: 'bar', label: 'Bar' }, - { value: 'block', label: 'Block' }, - { value: 'underline', label: 'Underline' } - ]} - /> - } - /> - - - - - updateSettings({ terminalCursorBlink: !settings.terminalCursorBlink }) - } - /> - - - - - updateSettings({ - terminalCursorOpacity: clampNumber(value, 0, 1) - }) - } - /> - -
-
- ) : null, - matchesSettingsSearch(searchQuery, TERMINAL_PANE_STYLE_SEARCH_ENTRIES) || + matchesSettingsSearch(searchQuery, TERMINAL_PANE_INTERACTION_SEARCH_ENTRIES) || (isWindows && matchesSettingsSearch(searchQuery, TERMINAL_RIGHT_CLICK_TO_PASTE_SEARCH_ENTRY)) ? ( -
+
- - - updateSettings({ - terminalInactivePaneOpacity: clampNumber(value, 0, 1) - }) - } - /> - - - - updateSettings({ - terminalDividerThicknessPx: clampNumber(value, 1, 32) - }) - } - /> - - {/* Why: the Windows-only right-click toggle lives in this section, so the section must also match that search term or settings search would hide the control even though it is present. */} @@ -687,30 +342,6 @@ export function TerminalPane({
) : null, - matchesSettingsSearch(searchQuery, TERMINAL_WINDOW_SEARCH_ENTRIES) ? ( - - ) : null, - matchesSettingsSearch(searchQuery, TERMINAL_DARK_THEME_SEARCH_ENTRIES) ? ( - - ) : null, - matchesSettingsSearch(searchQuery, TERMINAL_LIGHT_THEME_SEARCH_ENTRIES) ? ( - - ) : null, matchesSettingsSearch(searchQuery, TERMINAL_SETUP_SCRIPT_SEARCH_ENTRIES) ? (
))} - ) } diff --git a/src/renderer/src/components/settings/appearance-search.ts b/src/renderer/src/components/settings/appearance-search.ts index 0df7ac17a..989b1e64a 100644 --- a/src/renderer/src/components/settings/appearance-search.ts +++ b/src/renderer/src/components/settings/appearance-search.ts @@ -1,5 +1,6 @@ import type { StatusBarItem } from '../../../../shared/types' import type { SettingsSearchEntry } from './settings-search' +import { TERMINAL_APPEARANCE_SEARCH_ENTRIES } from './terminal-search' export const STATUS_BAR_TOGGLES: readonly { id: StatusBarItem @@ -123,6 +124,7 @@ export const APPEARANCE_PANE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ ...THEME_ENTRIES, ...TYPOGRAPHY_ENTRIES, ...ZOOM_ENTRIES, + ...TERMINAL_APPEARANCE_SEARCH_ENTRIES, ...LAYOUT_ENTRIES, ...TITLEBAR_ENTRIES, ...STATUS_BAR_ENTRIES, diff --git a/src/renderer/src/components/settings/terminal-search.test.ts b/src/renderer/src/components/settings/terminal-search.test.ts index 7b7b3f5ee..c5ef4800a 100644 --- a/src/renderer/src/components/settings/terminal-search.test.ts +++ b/src/renderer/src/components/settings/terminal-search.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { getTerminalPaneSearchEntries } from './terminal-search' +import { APPEARANCE_PANE_SEARCH_ENTRIES } from './appearance-search' describe('getTerminalPaneSearchEntries', () => { it('includes the Windows right-click setting on Windows', () => { @@ -66,12 +67,18 @@ describe('getTerminalPaneSearchEntries', () => { ).toBe(true) }) - it('includes the Ghostty import setting on all platforms', () => { + it('keeps terminal appearance settings in the Appearance search index', () => { const entriesWindows = getTerminalPaneSearchEntries({ isWindows: true, isMac: false }) const entriesMac = getTerminalPaneSearchEntries({ isWindows: false, isMac: true }) const entriesLinux = getTerminalPaneSearchEntries({ isWindows: false, isMac: false }) - expect(entriesWindows.some((entry) => entry.title === 'Import from Ghostty')).toBe(true) - expect(entriesMac.some((entry) => entry.title === 'Import from Ghostty')).toBe(true) - expect(entriesLinux.some((entry) => entry.title === 'Import from Ghostty')).toBe(true) + + expect(entriesWindows.some((entry) => entry.title === 'Import from Ghostty')).toBe(false) + expect(entriesMac.some((entry) => entry.title === 'Font Size')).toBe(false) + expect(entriesLinux.some((entry) => entry.title === 'Dark Theme')).toBe(false) + expect( + APPEARANCE_PANE_SEARCH_ENTRIES.some((entry) => entry.title === 'Import from Ghostty') + ).toBe(true) + expect(APPEARANCE_PANE_SEARCH_ENTRIES.some((entry) => entry.title === 'Font Size')).toBe(true) + expect(APPEARANCE_PANE_SEARCH_ENTRIES.some((entry) => entry.title === 'Dark Theme')).toBe(true) }) }) diff --git a/src/renderer/src/components/settings/terminal-search.ts b/src/renderer/src/components/settings/terminal-search.ts index 811a0343d..0773f85c3 100644 --- a/src/renderer/src/components/settings/terminal-search.ts +++ b/src/renderer/src/components/settings/terminal-search.ts @@ -79,7 +79,7 @@ export const TERMINAL_CURSOR_SEARCH_ENTRIES: SettingsSearchEntry[] = [ } ] -export const TERMINAL_PANE_STYLE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ +export const TERMINAL_PANE_APPEARANCE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ { title: 'Inactive Pane Opacity', description: 'Opacity applied to panes that are not currently active.', @@ -89,7 +89,10 @@ export const TERMINAL_PANE_STYLE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ title: 'Divider Thickness', description: 'Thickness of the pane divider line.', keywords: ['pane', 'divider', 'thickness'] - }, + } +] + +export const TERMINAL_PANE_INTERACTION_SEARCH_ENTRIES: SettingsSearchEntry[] = [ { title: 'Focus Follows Mouse', description: @@ -269,6 +272,16 @@ export const TERMINAL_SETUP_SCRIPT_SEARCH_ENTRIES: SettingsSearchEntry[] = [ } ] +export const TERMINAL_APPEARANCE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ + ...TERMINAL_TYPOGRAPHY_SEARCH_ENTRIES, + ...TERMINAL_CURSOR_SEARCH_ENTRIES, + ...TERMINAL_PANE_APPEARANCE_SEARCH_ENTRIES, + ...TERMINAL_DARK_THEME_SEARCH_ENTRIES, + ...TERMINAL_LIGHT_THEME_SEARCH_ENTRIES, + ...TERMINAL_WINDOW_SEARCH_ENTRIES, + ...TERMINAL_GHOSTTY_IMPORT_SEARCH_ENTRIES +] + export function getTerminalPaneSearchEntries(platform: { isWindows: boolean isMac: boolean @@ -277,16 +290,10 @@ export function getTerminalPaneSearchEntries(platform: { // platform-only controls out of other platforms' search results prevents // users from landing on an option the UI intentionally hides. return [ - ...TERMINAL_TYPOGRAPHY_SEARCH_ENTRIES, ...TERMINAL_RENDERING_SEARCH_ENTRIES, - ...TERMINAL_CURSOR_SEARCH_ENTRIES, - ...TERMINAL_PANE_STYLE_SEARCH_ENTRIES, + ...TERMINAL_PANE_INTERACTION_SEARCH_ENTRIES, ...(platform.isWindows ? TERMINAL_WINDOWS_SEARCH_ENTRIES : []), - ...TERMINAL_DARK_THEME_SEARCH_ENTRIES, - ...TERMINAL_LIGHT_THEME_SEARCH_ENTRIES, - ...TERMINAL_WINDOW_SEARCH_ENTRIES, ...TERMINAL_SETUP_SCRIPT_SEARCH_ENTRIES, - ...TERMINAL_GHOSTTY_IMPORT_SEARCH_ENTRIES, ...MANAGE_SESSIONS_SEARCH_ENTRIES, ...TERMINAL_ADVANCED_SEARCH_ENTRIES, ...(platform.isMac diff --git a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts index e39f0e725..514de5466 100644 --- a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts +++ b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts @@ -92,6 +92,9 @@ export function buildSettingsNavigationMetadata({ : RUNTIME_ENVIRONMENTS_SEARCH_ENTRY return [ + // Why: this array's order must mirror SETTINGS_NAV_GROUPS so the Settings + // sidebar and the Cmd+J palette both read top-to-bottom in the same grouped + // order — keep each new entry beside its group's siblings. { id: 'agents', title: 'Agents', @@ -173,34 +176,10 @@ export function buildSettingsNavigationMetadata({ searchEntries: TASKS_PANE_SEARCH_ENTRIES, group: 'workflows' }, - { - id: 'floating-workspace', - title: 'Floating Workspace', - description: 'Global terminal, browser, and markdown tabs.', - icon: PanelsTopLeft, - searchEntries: FLOATING_WORKSPACE_SEARCH_ENTRIES, - group: 'workflows' - }, - { - id: 'appearance', - title: 'Appearance', - description: 'Theme, zoom, app font, sidebars, and status bar.', - icon: Palette, - searchEntries: APPEARANCE_PANE_SEARCH_ENTRIES, - group: 'interface' - }, - { - id: 'input', - title: 'Input & Editing', - description: 'Selection and editing behavior.', - icon: TextCursorInput, - searchEntries: INPUT_PANE_SEARCH_ENTRIES, - group: 'interface' - }, { id: 'terminal', title: 'Terminal', - description: 'Shells, terminal appearance, and pane behavior.', + description: 'Shells, renderer, sessions, and terminal behavior.', icon: SquareTerminal, searchEntries: terminalPaneSearchEntries, group: 'workflows' @@ -222,7 +201,35 @@ export function buildSettingsNavigationMetadata({ icon: Globe, searchEntries: BROWSER_PANE_SEARCH_ENTRIES, group: 'workflows' - }, + } + ] + : []), + { + id: 'floating-workspace', + title: 'Floating Workspace', + description: 'Global terminal, browser, and markdown tabs.', + icon: PanelsTopLeft, + searchEntries: FLOATING_WORKSPACE_SEARCH_ENTRIES, + group: 'workflows' + }, + { + id: 'appearance', + title: 'Appearance', + description: 'Theme, zoom, app and terminal appearance, sidebars, and status bar.', + icon: Palette, + searchEntries: APPEARANCE_PANE_SEARCH_ENTRIES, + group: 'interface' + }, + { + id: 'input', + title: 'Input & Editing', + description: 'Selection and editing behavior.', + icon: TextCursorInput, + searchEntries: INPUT_PANE_SEARCH_ENTRIES, + group: 'interface' + }, + ...(showDesktopOnlySettings + ? [ { id: 'notifications', title: 'Notifications', @@ -233,6 +240,22 @@ export function buildSettingsNavigationMetadata({ } ] : []), + { + id: 'shortcuts', + title: 'Shortcuts', + description: 'Keyboard shortcuts for common actions.', + icon: Keyboard, + searchEntries: SHORTCUTS_PANE_SEARCH_ENTRIES, + group: 'interface' + }, + { + id: 'stats', + title: 'Stats & Usage', + description: 'Orca stats plus Claude, Codex, and OpenCode usage analytics.', + icon: BarChart3, + searchEntries: STATS_PANE_SEARCH_ENTRIES, + group: 'interface' + }, { id: 'servers', title: 'Remote Orca Servers', @@ -272,7 +295,7 @@ export function buildSettingsNavigationMetadata({ description: 'macOS privacy access for terminal-launched developer tools.', icon: ShieldCheck, searchEntries: DEVELOPER_PERMISSIONS_PANE_SEARCH_ENTRIES, - group: 'safety' + group: 'security' } ] : []), @@ -282,23 +305,7 @@ export function buildSettingsNavigationMetadata({ description: 'Anonymous usage data and telemetry controls.', icon: Lock, searchEntries: PRIVACY_PANE_SEARCH_ENTRIES, - group: 'safety' - }, - { - id: 'shortcuts', - title: 'Shortcuts', - description: 'Keyboard shortcuts for common actions.', - icon: Keyboard, - searchEntries: SHORTCUTS_PANE_SEARCH_ENTRIES, - group: 'interface' - }, - { - id: 'stats', - title: 'Stats & Usage', - description: 'Orca stats plus Claude, Codex, and OpenCode usage analytics.', - icon: BarChart3, - searchEntries: STATS_PANE_SEARCH_ENTRIES, - group: 'interface' + group: 'security' }, { id: 'experimental',