Improve settings organization (#2305)

This commit is contained in:
Jinjing 2026-05-18 21:55:16 -07:00 committed by GitHub
parent 0b29bfd9d3
commit f8a2f2889e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 310 additions and 255 deletions

View File

@ -807,19 +807,18 @@
@keyframes settings-section-flash {
0% {
border-color: var(--ring);
box-shadow:
0 0 0 2px color-mix(in srgb, var(--ring) 45%, transparent),
0 1px 2px 0 rgb(0 0 0 / 0.05);
background: color-mix(in srgb, var(--ring) 8%, transparent);
box-shadow: inset 3px 0 0 var(--ring);
}
60% {
border-color: var(--ring);
box-shadow:
0 0 0 2px color-mix(in srgb, var(--ring) 30%, transparent),
0 1px 2px 0 rgb(0 0 0 / 0.05);
background: color-mix(in srgb, var(--ring) 5%, transparent);
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--ring) 70%, transparent);
}
100% {
border-color: var(--border);
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
background: transparent;
box-shadow: inset 0 0 0 transparent;
}
}

View File

@ -252,14 +252,14 @@ export function AccountsPane({ settings, updateSettings }: AccountsPaneProps): R
Claude
</h3>
<p className="text-xs text-muted-foreground">
Add and switch Claude Code accounts without moving chat sessions to account-specific
config directories.
Optional. Orca can use your normal Claude login; add accounts only if you want quick
switching without moving chat sessions.
</p>
</div>
<SearchableSetting
title="Claude Accounts"
description="Manage which Claude account Orca materializes into the shared Claude auth files."
description="Optional account switcher for the shared Claude auth files."
keywords={['claude', 'account', 'rate limit', 'status bar', 'quota']}
className="space-y-3 px-1 py-2"
>
@ -419,7 +419,8 @@ export function AccountsPane({ settings, updateSettings }: AccountsPaneProps): R
Codex
</h3>
<p className="text-xs text-muted-foreground">
Add and switch between Codex accounts in Orca.
Optional. Orca can use your normal Codex login; add accounts only if you want quick
switching in Orca.
</p>
<p className="text-xs text-muted-foreground">
Each account keeps its own local sign-in context in Orca. Account auth stays on this

View File

@ -278,33 +278,33 @@ export function GeneralPane({ settings, updateSettings }: GeneralPaneProps): Rea
breaks that toast action even though this pane still renders fine. */}
<div id="general-skip-delete-worktree-confirm" className="scroll-mt-6">
<SearchableSetting
title="Skip Delete Worktree Confirmation"
description="Delete worktrees from the context menu without a confirmation dialog."
title="Ask Before Deleting Worktrees"
description="Show a confirmation dialog before deleting a worktree."
keywords={['delete', 'worktree', 'confirm', 'dialog', 'skip', 'prompt']}
className="flex items-center justify-between gap-4 px-1 py-2"
>
<div className="space-y-0.5">
<Label>Skip Delete Worktree Confirmation</Label>
<Label>Ask Before Deleting Worktrees</Label>
<p className="text-xs text-muted-foreground">
Delete worktrees from the context menu without a confirmation dialog. Errors still
surface as a toast with a Force Delete fallback.
Show a confirmation before deleting a worktree from the context menu. Failed deletes
still surface a Force Delete fallback.
</p>
</div>
<button
role="switch"
aria-checked={settings.skipDeleteWorktreeConfirm}
aria-checked={!settings.skipDeleteWorktreeConfirm}
onClick={() =>
updateSettings({
skipDeleteWorktreeConfirm: !settings.skipDeleteWorktreeConfirm
})
}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors ${
settings.skipDeleteWorktreeConfirm ? 'bg-foreground' : 'bg-muted-foreground/30'
!settings.skipDeleteWorktreeConfirm ? 'bg-foreground' : 'bg-muted-foreground/30'
}`}
>
<span
className={`pointer-events-none block size-3.5 rounded-full bg-background shadow-sm transition-transform ${
settings.skipDeleteWorktreeConfirm ? 'translate-x-4' : 'translate-x-0.5'
!settings.skipDeleteWorktreeConfirm ? 'translate-x-4' : 'translate-x-0.5'
}`}
/>
</button>
@ -313,32 +313,32 @@ export function GeneralPane({ settings, updateSettings }: GeneralPaneProps): Rea
<div id="general-skip-delete-automation-confirm" className="scroll-mt-6">
<SearchableSetting
title="Skip Delete Automation Confirmation"
description="Delete automations without a confirmation dialog."
title="Ask Before Deleting Automations"
description="Show a confirmation dialog before deleting an automation and its run history."
keywords={['delete', 'automation', 'confirm', 'dialog', 'skip', 'prompt']}
className="flex items-center justify-between gap-4 px-1 py-2"
>
<div className="space-y-0.5">
<Label>Skip Delete Automation Confirmation</Label>
<Label>Ask Before Deleting Automations</Label>
<p className="text-xs text-muted-foreground">
Delete automations and their run history without a confirmation dialog.
Show a confirmation before deleting automations and their run history.
</p>
</div>
<button
role="switch"
aria-checked={settings.skipDeleteAutomationConfirm}
aria-checked={!settings.skipDeleteAutomationConfirm}
onClick={() =>
updateSettings({
skipDeleteAutomationConfirm: !settings.skipDeleteAutomationConfirm
})
}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors ${
settings.skipDeleteAutomationConfirm ? 'bg-foreground' : 'bg-muted-foreground/30'
!settings.skipDeleteAutomationConfirm ? 'bg-foreground' : 'bg-muted-foreground/30'
}`}
>
<span
className={`pointer-events-none block size-3.5 rounded-full bg-background shadow-sm transition-transform ${
settings.skipDeleteAutomationConfirm ? 'translate-x-4' : 'translate-x-0.5'
!settings.skipDeleteAutomationConfirm ? 'translate-x-4' : 'translate-x-0.5'
}`}
/>
</button>

View File

@ -116,9 +116,26 @@ type SettingsNavSection = {
description: string
icon: typeof SlidersHorizontal
searchEntries: SettingsSearchEntry[]
group: string
badge?: string
}
type SettingsNavGroup = {
id: string
title: string
sections: SettingsNavSection[]
}
const SETTINGS_NAV_GROUPS = [
{ id: 'setup', title: 'Set Up' },
{ id: 'workflows', title: 'Workflows' },
{ id: 'interface', title: 'Interface' },
{ id: 'capabilities', title: 'AI Capabilities' },
{ id: 'remote', title: 'Remote Access' },
{ id: 'safety', title: 'Safety' },
{ id: 'experimental', title: 'Experimental' }
] as const
function getSettingsSectionId(pane: SettingsNavTarget, repoId: string | null): string {
if (pane === 'repo' && repoId) {
return `repo-${repoId}`
@ -416,61 +433,78 @@ function Settings(): React.JSX.Element {
{
id: 'general',
title: 'General',
description: 'Workspace, editor, and updates.',
description: 'Workspace defaults, app setup, and maintenance.',
icon: SlidersHorizontal,
searchEntries: GENERAL_PANE_SEARCH_ENTRIES
searchEntries: GENERAL_PANE_SEARCH_ENTRIES,
group: 'setup'
},
{
id: 'agents',
title: 'Agents',
description: 'Manage AI agents, set a default, and customize commands.',
icon: Bot,
searchEntries: AGENTS_PANE_SEARCH_ENTRIES
searchEntries: AGENTS_PANE_SEARCH_ENTRIES,
group: 'setup'
},
{
id: 'accounts',
title: 'Agent Accounts',
description: 'Sign in and switch between Claude, Codex, Gemini, and OpenCode Go accounts.',
title: 'AI Provider Accounts',
description: 'Optional account switching for Claude, Codex, Gemini, and OpenCode Go.',
icon: UserCog,
searchEntries: ACCOUNTS_PANE_SEARCH_ENTRIES
searchEntries: ACCOUNTS_PANE_SEARCH_ENTRIES,
group: 'setup',
badge: 'Optional'
},
{
id: 'integrations',
title: 'Integrations',
description: 'Connect GitHub, GitLab, Linear, and source-hosting services.',
icon: Blocks,
searchEntries: INTEGRATIONS_PANE_SEARCH_ENTRIES,
group: 'setup'
},
{
id: 'git',
title: 'Git',
description: 'Branch naming, local ref behavior, and AI commit messages.',
title: 'Git & Source Control',
description: 'Branch naming, base refs, attribution, and AI commit messages.',
icon: GitBranch,
// Why: the AI commit messages pane is rendered inside the Git section,
// so its search entries belong to Git too — that way a query like
// "claude" or "thinking" still surfaces the section.
searchEntries: [...GIT_PANE_SEARCH_ENTRIES, ...COMMIT_MESSAGE_AI_PANE_SEARCH_ENTRIES]
searchEntries: [...GIT_PANE_SEARCH_ENTRIES, ...COMMIT_MESSAGE_AI_PANE_SEARCH_ENTRIES],
group: 'workflows'
},
{
id: 'tasks',
title: 'Tasks',
title: 'Task Sources',
description: 'Choose which task providers appear in the Tasks page and sidebar.',
icon: ListChecks,
searchEntries: TASKS_PANE_SEARCH_ENTRIES
searchEntries: TASKS_PANE_SEARCH_ENTRIES,
group: 'workflows'
},
{
id: 'appearance',
title: 'Appearance',
description: 'Theme and UI scaling.',
description: 'Theme, zoom, app font, sidebars, and status bar.',
icon: Palette,
searchEntries: APPEARANCE_PANE_SEARCH_ENTRIES
searchEntries: APPEARANCE_PANE_SEARCH_ENTRIES,
group: 'interface'
},
{
id: 'input',
title: 'Input & Editing',
description: 'Selection and editing behavior.',
icon: TextCursorInput,
searchEntries: INPUT_PANE_SEARCH_ENTRIES
searchEntries: INPUT_PANE_SEARCH_ENTRIES,
group: 'interface'
},
{
id: 'terminal',
title: 'Terminal',
description: 'Terminal appearance, previews, and defaults for new panes.',
description: 'Shells, terminal appearance, quick commands, and pane behavior.',
icon: SquareTerminal,
searchEntries: terminalPaneSearchEntries
searchEntries: terminalPaneSearchEntries,
group: 'workflows'
},
...(showDesktopOnlySettings
? [
@ -479,14 +513,16 @@ function Settings(): React.JSX.Element {
title: 'Browser',
description: 'Home page, link routing, and session cookies.',
icon: Globe,
searchEntries: BROWSER_PANE_SEARCH_ENTRIES
searchEntries: BROWSER_PANE_SEARCH_ENTRIES,
group: 'workflows'
},
{
id: 'notifications' as const,
title: 'Notifications',
description: 'Native desktop notifications for agent and terminal events.',
icon: Bell,
searchEntries: NOTIFICATIONS_PANE_SEARCH_ENTRIES
searchEntries: NOTIFICATIONS_PANE_SEARCH_ENTRIES,
group: 'interface'
}
]
: []),
@ -495,26 +531,37 @@ function Settings(): React.JSX.Element {
title: 'Orchestration',
description: 'Coordinate multiple coding agents through Orca.',
icon: Network,
searchEntries: ORCHESTRATION_PANE_SEARCH_ENTRIES
searchEntries: ORCHESTRATION_PANE_SEARCH_ENTRIES,
group: 'capabilities'
},
{
id: 'servers',
title: 'Servers',
title: 'Remote Orca Servers',
description: isWebClient
? 'Connect this browser to a saved Orca server.'
: 'Run this client locally or through a remote Orca server.',
: 'Switch between local desktop mode and paired remote Orca runtimes.',
icon: Server,
searchEntries: [runtimeEnvironmentsSearchEntry],
group: 'remote',
badge: 'Beta'
},
...(showDesktopOnlySettings
? [
{
id: 'ssh' as const,
title: 'SSH Hosts',
description: 'Remote SSH hosts for files, terminals, and git.',
icon: Cable,
searchEntries: SSH_PANE_SEARCH_ENTRIES,
group: 'remote'
},
{
id: 'mobile' as const,
title: 'Mobile',
description: 'Control terminals and agents from your phone.',
icon: Smartphone,
searchEntries: MOBILE_SETTINGS_PANE_SEARCH_ENTRIES,
group: 'remote',
badge: 'Beta'
},
{
@ -523,6 +570,7 @@ function Settings(): React.JSX.Element {
description: 'Enable agents to control any app on your computer.',
icon: MousePointerClick,
searchEntries: COMPUTER_USE_PANE_SEARCH_ENTRIES,
group: 'capabilities',
badge: 'Beta'
},
{
@ -531,6 +579,7 @@ function Settings(): React.JSX.Element {
description: 'Local speech-to-text dictation with on-device models.',
icon: Mic,
searchEntries: VOICE_PANE_SEARCH_ENTRIES,
group: 'capabilities',
badge: 'Beta'
}
]
@ -539,10 +588,11 @@ function Settings(): React.JSX.Element {
? [
{
id: 'developer-permissions' as const,
title: 'Permissions',
title: 'macOS Permissions',
description: 'macOS privacy access for terminal-launched developer tools.',
icon: ShieldCheck,
searchEntries: DEVELOPER_PERMISSIONS_PANE_SEARCH_ENTRIES
searchEntries: DEVELOPER_PERMISSIONS_PANE_SEARCH_ENTRIES,
group: 'safety'
}
]
: []),
@ -551,53 +601,40 @@ function Settings(): React.JSX.Element {
title: 'Privacy & Telemetry',
description: 'Anonymous usage data and telemetry controls.',
icon: Lock,
searchEntries: PRIVACY_PANE_SEARCH_ENTRIES
searchEntries: PRIVACY_PANE_SEARCH_ENTRIES,
group: 'safety'
},
{
id: 'shortcuts',
title: 'Shortcuts',
description: 'Keyboard shortcuts for common actions.',
icon: Keyboard,
searchEntries: SHORTCUTS_PANE_SEARCH_ENTRIES
},
{
id: 'integrations',
title: 'Integrations',
description: 'GitHub, Linear, and other service connections.',
icon: Blocks,
searchEntries: INTEGRATIONS_PANE_SEARCH_ENTRIES
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
searchEntries: STATS_PANE_SEARCH_ENTRIES,
group: 'interface'
},
...(showDesktopOnlySettings
? [
{
id: 'ssh' as const,
title: 'SSH',
description: 'Remote SSH connections.',
icon: Cable,
searchEntries: SSH_PANE_SEARCH_ENTRIES
}
]
: []),
{
id: 'experimental',
title: 'Experimental',
description: 'New features that are still taking shape. Give them a try.',
icon: FlaskConical,
searchEntries: EXPERIMENTAL_PANE_SEARCH_ENTRIES
searchEntries: EXPERIMENTAL_PANE_SEARCH_ENTRIES,
group: 'experimental'
},
...repos.map((repo) => ({
id: `repo-${repo.id}`,
title: repo.displayName,
description: `${getRepoKindLabel(repo)}${repo.path}`,
icon: SlidersHorizontal,
searchEntries: getRepositoryPaneSearchEntries(repo)
searchEntries: getRepositoryPaneSearchEntries(repo),
group: 'repositories'
}))
],
[
@ -958,6 +995,10 @@ function Settings(): React.JSX.Element {
}
const generalNavSections = visibleNavSections.filter((section) => !section.id.startsWith('repo-'))
const generalNavGroups: SettingsNavGroup[] = SETTINGS_NAV_GROUPS.map((group) => ({
...group,
sections: generalNavSections.filter((section) => section.group === group.id)
})).filter((group) => group.sections.length > 0)
const repoNavSections = visibleNavSections
.filter((section) => section.id.startsWith('repo-'))
.map((section) => {
@ -970,7 +1011,7 @@ function Settings(): React.JSX.Element {
<div className="settings-view-shell flex min-h-0 flex-1 overflow-hidden bg-background">
<SettingsSidebar
activeSectionId={activeSectionId}
generalSections={generalNavSections}
generalGroups={generalNavGroups}
repoSections={repoNavSections}
hasRepos={repos.length > 0}
searchQuery={settingsSearchInputQuery}
@ -992,7 +1033,7 @@ function Settings(): React.JSX.Element {
<SettingsSection
id="general"
title="General"
description="Workspace, editor, and updates."
description="Workspace defaults, app setup, and maintenance."
searchEntries={GENERAL_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('general') ? (
@ -1000,15 +1041,6 @@ function Settings(): React.JSX.Element {
) : null}
</SettingsSection>
<SettingsSection
id="integrations"
title="Integrations"
description="GitHub, Linear, and other service connections."
searchEntries={INTEGRATIONS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('integrations') ? <IntegrationsPane /> : null}
</SettingsSection>
<SettingsSection
id="agents"
title="Agents"
@ -1022,8 +1054,9 @@ function Settings(): React.JSX.Element {
<SettingsSection
id="accounts"
title="Agent Accounts"
description="Sign in and switch between Claude, Codex, Gemini, and OpenCode Go accounts."
title="AI Provider Accounts"
description="Optional. Orca works with your existing provider logins; add accounts only if you want Orca to help switch between them."
badge="Optional"
searchEntries={ACCOUNTS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('accounts') ? (
@ -1031,10 +1064,19 @@ function Settings(): React.JSX.Element {
) : null}
</SettingsSection>
<SettingsSection
id="integrations"
title="Integrations"
description="Connect GitHub, GitLab, Linear, and source-hosting services."
searchEntries={INTEGRATIONS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('integrations') ? <IntegrationsPane /> : null}
</SettingsSection>
<SettingsSection
id="git"
title="Git"
description="Branch naming, local ref behavior, and AI commit messages."
title="Git & Source Control"
description="Branch naming, base refs, attribution, and AI commit messages."
searchEntries={[
...GIT_PANE_SEARCH_ENTRIES,
...COMMIT_MESSAGE_AI_PANE_SEARCH_ENTRIES
@ -1060,7 +1102,7 @@ function Settings(): React.JSX.Element {
<SettingsSection
id="tasks"
title="Tasks"
title="Task Sources"
description="Choose which task providers appear in the Tasks page and sidebar."
searchEntries={TASKS_PANE_SEARCH_ENTRIES}
>
@ -1069,35 +1111,10 @@ function Settings(): React.JSX.Element {
) : null}
</SettingsSection>
<SettingsSection
id="appearance"
title="Appearance"
description="Theme and UI scaling."
searchEntries={APPEARANCE_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('appearance') ? (
<AppearancePane
settings={settings}
updateSettings={updateSettings}
applyTheme={applyTheme}
fontSuggestions={fontSuggestions}
/>
) : null}
</SettingsSection>
<SettingsSection
id="input"
title="Input & Editing"
description="Selection and editing behavior."
searchEntries={INPUT_PANE_SEARCH_ENTRIES}
>
<InputPane settings={settings} updateSettings={updateSettings} />
</SettingsSection>
<SettingsSection
id="terminal"
title="Terminal"
description="Terminal appearance, previews, and defaults for new panes."
description="Shells, terminal appearance, quick commands, and pane behavior."
searchEntries={terminalPaneSearchEntries}
headerAction={
<Button
@ -1129,35 +1146,78 @@ function Settings(): React.JSX.Element {
</SettingsSection>
{showDesktopOnlySettings ? (
<>
<SettingsSection
id="browser"
title="Browser"
description="Home page, link routing, and session cookies."
searchEntries={BROWSER_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('browser') ? (
<BrowserPane
settings={settings}
updateSettings={updateSettings}
onOpenComputerUse={openComputerUseFromBrowser}
/>
) : null}
</SettingsSection>
<SettingsSection
id="notifications"
title="Notifications"
description="Native desktop notifications for agent activity and terminal events."
searchEntries={NOTIFICATIONS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('notifications') ? (
<NotificationsPane settings={settings} updateSettings={updateSettings} />
) : null}
</SettingsSection>
</>
<SettingsSection
id="browser"
title="Browser"
description="Home page, link routing, and session cookies."
searchEntries={BROWSER_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('browser') ? (
<BrowserPane
settings={settings}
updateSettings={updateSettings}
onOpenComputerUse={openComputerUseFromBrowser}
/>
) : null}
</SettingsSection>
) : null}
<SettingsSection
id="appearance"
title="Appearance"
description="Theme, zoom, app font, sidebars, and status bar."
searchEntries={APPEARANCE_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('appearance') ? (
<AppearancePane
settings={settings}
updateSettings={updateSettings}
applyTheme={applyTheme}
fontSuggestions={fontSuggestions}
/>
) : null}
</SettingsSection>
<SettingsSection
id="input"
title="Input & Editing"
description="Selection and editing behavior."
searchEntries={INPUT_PANE_SEARCH_ENTRIES}
>
<InputPane settings={settings} updateSettings={updateSettings} />
</SettingsSection>
{showDesktopOnlySettings ? (
<SettingsSection
id="notifications"
title="Notifications"
description="Native desktop notifications for agent activity and terminal events."
searchEntries={NOTIFICATIONS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('notifications') ? (
<NotificationsPane settings={settings} updateSettings={updateSettings} />
) : null}
</SettingsSection>
) : null}
<SettingsSection
id="shortcuts"
title="Shortcuts"
description="Keyboard shortcuts for common actions."
searchEntries={SHORTCUTS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('shortcuts') ? <ShortcutsPane /> : null}
</SettingsSection>
<SettingsSection
id="stats"
title="Stats & Usage"
description="Orca stats plus Claude, Codex, and OpenCode usage analytics."
searchEntries={STATS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('stats') ? <StatsPane /> : null}
</SettingsSection>
<SettingsSection
id="orchestration"
title="Orchestration"
@ -1167,41 +1227,8 @@ function Settings(): React.JSX.Element {
{isSectionMounted('orchestration') ? <OrchestrationPane /> : null}
</SettingsSection>
<SettingsSection
id="servers"
title="Servers"
badge="Beta"
description={
isWebClient
? 'Connect this browser to a saved Orca server.'
: 'Run this desktop client locally or through a remote Orca server.'
}
searchEntries={[runtimeEnvironmentsSearchEntry]}
>
{isSectionMounted('servers') ? (
<RuntimeEnvironmentsPane
settings={settings}
switchRuntimeEnvironment={switchRuntimeEnvironment}
canGeneratePairingUrl={!isWebClient}
allowLocalRuntime={!isWebClient}
/>
) : null}
</SettingsSection>
{showDesktopOnlySettings ? (
<>
<SettingsSection
id="mobile"
title="Mobile"
badge="Beta"
description="Control terminals and agents from your phone."
searchEntries={MOBILE_SETTINGS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('mobile') ? (
<MobileSettingsPane settings={settings} updateSettings={updateSettings} />
) : null}
</SettingsSection>
<SettingsSection
id="computer-use"
title="Computer Use"
@ -1249,10 +1276,56 @@ function Settings(): React.JSX.Element {
</>
) : null}
<SettingsSection
id="servers"
title="Remote Orca Servers"
badge="Beta"
description={
isWebClient
? 'Connect this browser to a saved Orca server.'
: 'Switch between local desktop mode and paired remote Orca runtimes.'
}
searchEntries={[runtimeEnvironmentsSearchEntry]}
>
{isSectionMounted('servers') ? (
<RuntimeEnvironmentsPane
settings={settings}
switchRuntimeEnvironment={switchRuntimeEnvironment}
canGeneratePairingUrl={!isWebClient}
allowLocalRuntime={!isWebClient}
/>
) : null}
</SettingsSection>
{showDesktopOnlySettings ? (
<>
<SettingsSection
id="ssh"
title="SSH Hosts"
description="Remote SSH hosts for files, terminals, and git."
searchEntries={SSH_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('ssh') ? <SshPane /> : null}
</SettingsSection>
<SettingsSection
id="mobile"
title="Mobile"
badge="Beta"
description="Control terminals and agents from your phone."
searchEntries={MOBILE_SETTINGS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('mobile') ? (
<MobileSettingsPane settings={settings} updateSettings={updateSettings} />
) : null}
</SettingsSection>
</>
) : null}
{showDesktopOnlySettings && isMac ? (
<SettingsSection
id="developer-permissions"
title="Permissions"
title="macOS Permissions"
description="macOS privacy access for terminal-launched developer tools."
searchEntries={DEVELOPER_PERMISSIONS_PANE_SEARCH_ENTRIES}
>
@ -1271,35 +1344,6 @@ function Settings(): React.JSX.Element {
{isSectionMounted('privacy') ? <PrivacyPane settings={settings} /> : null}
</SettingsSection>
<SettingsSection
id="shortcuts"
title="Shortcuts"
description="Keyboard shortcuts for common actions."
searchEntries={SHORTCUTS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('shortcuts') ? <ShortcutsPane /> : null}
</SettingsSection>
<SettingsSection
id="stats"
title="Stats"
description="How much Orca has helped you."
searchEntries={STATS_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('stats') ? <StatsPane /> : null}
</SettingsSection>
{showDesktopOnlySettings ? (
<SettingsSection
id="ssh"
title="SSH"
description="Manage remote SSH connections. Connect to remote servers to browse files, run terminals, and use git."
searchEntries={SSH_PANE_SEARCH_ENTRIES}
>
{isSectionMounted('ssh') ? <SshPane /> : null}
</SettingsSection>
) : null}
<SettingsSection
id="experimental"
title="Experimental"

View File

@ -41,16 +41,14 @@ export function SettingsSection({
id={id}
data-settings-section={id}
className={
// Why: these sections already contain many internal borders and cards, so a lone divider
// line gets lost in the visual noise. Giving each section its own padded surface creates a
// clear outer silhouette that still works when the inner content changes.
className ??
'scroll-mt-6 space-y-8 rounded-2xl border border-border/60 bg-card/35 px-6 py-6 shadow-sm'
// Why: each pane already owns internal cards and borders. A stronger unframed section
// break keeps top-level settings pages distinct without nesting everything in cards.
className ?? 'scroll-mt-6 space-y-6 border-b-2 border-foreground/20 pb-10 last:border-b-0'
}
>
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<h2 className="flex items-center gap-2 text-xl font-semibold">
<h2 className="flex items-center gap-2 text-lg font-semibold">
{title}
{badge ? (
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">

View File

@ -13,6 +13,12 @@ type NavSection = {
badge?: string
}
type NavGroup = {
id: string
title: string
sections: NavSection[]
}
type RepoNavSection = NavSection & {
badgeColor?: string
isRemote?: boolean
@ -20,7 +26,7 @@ type RepoNavSection = NavSection & {
type SettingsSidebarProps = {
activeSectionId: string
generalSections: NavSection[]
generalGroups: NavGroup[]
repoSections: RepoNavSection[]
hasRepos: boolean
searchQuery: string
@ -35,7 +41,7 @@ type SettingsSidebarProps = {
export function SettingsSidebar({
activeSectionId,
generalSections,
generalGroups,
repoSections,
hasRepos,
searchQuery,
@ -78,39 +84,46 @@ export function SettingsSidebar({
<div className="min-h-0 flex-1 overflow-y-auto scrollbar-sleek px-3 py-4">
<div className="space-y-5">
<div className="space-y-1">
{generalSections.map((section) => {
const Icon = section.icon
const isActive = activeSectionId === section.id
{generalGroups.map((group) => (
<div key={group.id} className="space-y-2">
<p className="px-3 text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">
{group.title}
</p>
<div className="space-y-1">
{group.sections.map((section) => {
const Icon = section.icon
const isActive = activeSectionId === section.id
return (
<button
key={section.id}
onClick={(event) =>
onSelectSection(section.id, {
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey
})
}
className={`flex w-full items-center rounded-lg px-3 py-2 text-left text-sm transition-colors ${
isActive
? 'bg-accent font-medium text-accent-foreground'
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
}`}
>
<Icon className="mr-2 size-4" />
{section.title}
{section.badge ? (
<span className="ml-auto rounded-full bg-muted px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wider text-muted-foreground">
{section.badge}
</span>
) : null}
</button>
)
})}
</div>
return (
<button
key={section.id}
onClick={(event) =>
onSelectSection(section.id, {
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey
})
}
className={`flex w-full items-center rounded-lg px-3 py-2 text-left text-sm transition-colors ${
isActive
? 'bg-accent font-medium text-accent-foreground'
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
}`}
>
<Icon className="mr-2 size-4" />
<span className="truncate">{section.title}</span>
{section.badge ? (
<span className="ml-auto rounded-full bg-muted px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wider text-muted-foreground">
{section.badge}
</span>
) : null}
</button>
)
})}
</div>
</div>
))}
<div className="space-y-2">
<p className="px-3 text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">

View File

@ -3,21 +3,21 @@ import type { SettingsSearchEntry } from './settings-search'
export const ACCOUNTS_CLAUDE_SEARCH_ENTRIES: SettingsSearchEntry[] = [
{
title: 'Claude Accounts',
description: 'Manage which Claude account Orca uses while preserving shared chat context.',
keywords: ['claude', 'account', 'switch', 'active', 'status bar', 'quota']
description: 'Optional account switching for Claude while preserving shared chat context.',
keywords: ['claude', 'account', 'switch', 'active', 'status bar', 'quota', 'optional']
}
]
export const ACCOUNTS_CODEX_SEARCH_ENTRIES: SettingsSearchEntry[] = [
{
title: 'Codex Accounts',
description: 'Manage which Codex account Orca uses for live rate limit fetching.',
keywords: ['codex', 'account', 'rate limit', 'status bar', 'quota']
description: 'Optional account switching for Codex and live rate limit fetching.',
keywords: ['codex', 'account', 'rate limit', 'status bar', 'quota', 'optional']
},
{
title: 'Active Codex Account',
description: 'Choose which saved Codex account powers live quota reads.',
keywords: ['codex', 'account', 'switch', 'active', 'status bar']
description: 'Choose which optional saved Codex account powers live quota reads.',
keywords: ['codex', 'account', 'switch', 'active', 'status bar', 'optional']
}
]

View File

@ -12,13 +12,13 @@ export const GENERAL_WORKSPACE_SEARCH_ENTRIES: SettingsSearchEntry[] = [
keywords: ['nested', 'subfolder', 'directory']
},
{
title: 'Skip Delete Worktree Confirmation',
description: 'Delete worktrees from the context menu without a confirmation dialog.',
title: 'Ask Before Deleting Worktrees',
description: 'Show a confirmation dialog before deleting a worktree.',
keywords: ['delete', 'worktree', 'confirm', 'dialog', 'skip', 'prompt']
},
{
title: 'Skip Delete Automation Confirmation',
description: 'Delete automations without a confirmation dialog.',
title: 'Ask Before Deleting Automations',
description: 'Show a confirmation dialog before deleting an automation and its run history.',
keywords: ['delete', 'automation', 'confirm', 'dialog', 'skip', 'prompt']
},
{

View File

@ -300,7 +300,7 @@ export function SshStatusSegment({
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={() => {
openSettingsTarget({ pane: 'general', repoId: null, sectionId: 'ssh' })
openSettingsTarget({ pane: 'ssh', repoId: null, sectionId: 'ssh' })
setActiveView('settings')
}}
>