feat(settings): focus search on Cmd/Ctrl+F and fix empty section headers (#1485)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson 2026-05-05 20:06:42 -07:00 committed by GitHub
parent 2f99f93142
commit be2b30f1ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 62 additions and 6 deletions

View File

@ -428,7 +428,16 @@ export function AccountsPane({ settings, updateSettings }: AccountsPaneProps): R
<SearchableSetting
title="Codex Accounts"
description="Manage which Codex account Orca uses for live rate limit fetching."
keywords={['codex', 'account', 'rate limit', 'status bar', 'quota']}
// Why: this single SearchableSetting backs the whole Codex section,
// including the "Active Codex Account" sub-control (account picker
// below). Roll every Codex search entry's title/description/keywords
// into one haystack so a search for "Active Codex Account" doesn't
// render the section header with no body underneath it.
keywords={ACCOUNTS_CODEX_SEARCH_ENTRIES.flatMap((entry) => [
entry.title,
entry.description ?? '',
...(entry.keywords ?? [])
])}
className="space-y-3 px-1 py-2"
>
{/* Why: Settings deep-links can target this subsection directly from

View File

@ -422,7 +422,15 @@ export function GeneralPane({ settings, updateSettings }: GeneralPaneProps): Rea
<SearchableSetting
title="Cache Timer"
description="Show a countdown after a Claude agent becomes idle."
keywords={['cache', 'timer', 'prompt', 'ttl', 'claude']}
// Why: this is the primary control for the section gated by
// GENERAL_CACHE_TIMER_SEARCH_ENTRIES (title "Prompt Cache Timer").
// Mirroring those keywords keeps a search for "Prompt Cache Timer"
// from rendering the section header with no body underneath.
keywords={GENERAL_CACHE_TIMER_SEARCH_ENTRIES.flatMap((entry) => [
entry.title,
entry.description ?? '',
...(entry.keywords ?? [])
])}
className="flex items-center justify-between gap-4 px-1 py-2"
>
<div className="space-y-0.5">

View File

@ -17,6 +17,7 @@ import {
} from '../ui/dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'
import { SearchableSetting } from './SearchableSetting'
import { MANAGE_SESSIONS_SEARCH_ENTRIES } from './terminal-search'
import { useAppStore } from '../../store'
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
import { activateTabAndFocusPane } from '@/lib/activate-tab-and-focus-pane'
@ -285,9 +286,9 @@ export function ManageSessionsSection(): React.JSX.Element {
</div>
<SearchableSetting
title="Daemon sessions running"
description="Count of live PTY sessions across all workspaces."
keywords={['daemon', 'pty', 'sessions', 'manage', 'kill', 'restart', 'terminal']}
title={MANAGE_SESSIONS_SEARCH_ENTRIES[0].title}
description={MANAGE_SESSIONS_SEARCH_ENTRIES[0].description}
keywords={MANAGE_SESSIONS_SEARCH_ENTRIES[0].keywords}
className="space-y-3"
>
{/* Why: full-width sessions card. The table *is* the primary

View File

@ -192,6 +192,7 @@ function Settings(): React.JSX.Element {
// leak through into a normal reopen of Settings.
const [hiddenExperimentalUnlocked, setHiddenExperimentalUnlocked] = useState(false)
const contentScrollRef = useRef<HTMLDivElement | null>(null)
const searchInputRef = useRef<HTMLInputElement | null>(null)
const terminalFontsLoadedRef = useRef(false)
const pendingNavSectionRef = useRef<string | null>(null)
const pendingScrollTargetRef = useRef<string | null>(null)
@ -220,6 +221,30 @@ function Settings(): React.JSX.Element {
return () => document.removeEventListener('keydown', handleKeyDown)
}, [closeSettingsPage])
useEffect(() => {
const handleFindShortcut = (event: KeyboardEvent): void => {
if (event.defaultPrevented || event.altKey || event.shiftKey) {
return
}
// Why: Cmd on Mac, Ctrl elsewhere — matches the rest of the app's
// mod-key convention (see App.tsx) and aligns with platform Find norms.
const mod = isMac ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey
if (!mod || event.key.toLowerCase() !== 'f') {
return
}
const input = searchInputRef.current
if (!input) {
return
}
event.preventDefault()
input.focus()
input.select()
}
document.addEventListener('keydown', handleFindShortcut)
return () => document.removeEventListener('keydown', handleFindShortcut)
}, [isMac])
useEffect(
() => () => {
// Why: the settings search is a transient in-page filter. Leaving it behind makes the next
@ -603,6 +628,7 @@ function Settings(): React.JSX.Element {
repoSections={repoNavSections}
hasRepos={repos.length > 0}
searchQuery={settingsSearchQuery}
searchInputRef={searchInputRef}
onBack={closeSettingsPage}
onSearchChange={setSettingsSearchQuery}
onSelectSection={scrollToSection}

View File

@ -1,7 +1,11 @@
import type { RefObject } from 'react'
import { ArrowLeft, Search, Server, type LucideIcon, type LucideProps } from 'lucide-react'
import { isMacUserAgent } from '@/components/terminal-pane/pane-helpers'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
const SEARCH_SHORTCUT_HINT = isMacUserAgent() ? '⌘F' : 'Ctrl+F'
type NavSection = {
id: string
title: string
@ -20,6 +24,7 @@ type SettingsSidebarProps = {
repoSections: RepoNavSection[]
hasRepos: boolean
searchQuery: string
searchInputRef?: RefObject<HTMLInputElement | null>
onBack: () => void
onSearchChange: (query: string) => void
onSelectSection: (
@ -34,6 +39,7 @@ export function SettingsSidebar({
repoSections,
hasRepos,
searchQuery,
searchInputRef,
onBack,
onSearchChange,
onSelectSection
@ -56,11 +62,17 @@ export function SettingsSidebar({
<div className="relative">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
ref={searchInputRef}
value={searchQuery}
onChange={(event) => onSearchChange(event.target.value)}
placeholder="Search settings"
className="pl-9"
className="pl-9 pr-14"
/>
{searchQuery === '' ? (
<kbd className="pointer-events-none absolute right-2 top-1/2 inline-flex -translate-y-1/2 items-center rounded border border-border/60 bg-background/40 px-1.5 py-px font-mono text-[10px] font-medium text-muted-foreground">
{SEARCH_SHORTCUT_HINT}
</kbd>
) : null}
</div>
</div>