Improve Settings setup guide entry (#4460)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson
2026-06-02 17:10:34 -07:00
committed by GitHub
co-authored by Orca
parent d879cfde37
commit 019165a4cd
14 changed files with 410 additions and 161 deletions
@@ -1,5 +1,4 @@
import type { LucideIcon } from 'lucide-react'
import type { SettingsNavSection } from '@/lib/settings-navigation-types'
import type { SettingsNavIcon, SettingsNavSection } from '@/lib/settings-navigation-types'
import type { CmdJQuickAction } from './quick-actions'
export type CmdJSettingsResult = {
@@ -7,7 +6,7 @@ export type CmdJSettingsResult = {
kind: 'settings'
title: string
description: string
icon: LucideIcon
icon: SettingsNavIcon
sectionId: string
targetSectionId?: string
order: number
@@ -224,7 +224,7 @@ export function FeatureWallSetupChecklist(
const setupSteps = getFeatureWallSetupStepsForSection('setup')
return (
<div className="grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] gap-5 lg:grid-cols-[minmax(280px,360px)_minmax(0,1fr)] lg:grid-rows-[minmax(0,1fr)]">
<div className="grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] gap-5 lg:grid-cols-[minmax(200px,300px)_minmax(0,1fr)] lg:grid-rows-[minmax(0,1fr)]">
<div className="scrollbar-sleek min-h-0 space-y-5 overflow-y-auto pr-1">
<SetupSection
title="Setup"
@@ -45,7 +45,7 @@ import { RuntimeEnvironmentsPane } from './RuntimeEnvironmentsPane'
import { PrivacyPane } from './PrivacyPane'
import { AdvancedPane } from './AdvancedPane'
import { SettingsSidebar } from './SettingsSidebar'
import { SettingsSetupGuideCard } from './SettingsSetupGuideCard'
import { SettingsSetupGuidePane } from './SettingsSetupGuidePane'
import { ActiveSettingsSectionProvider, SettingsSection } from './SettingsSection'
import { matchesSettingsSearch } from './settings-search'
import { cn } from '@/lib/utils'
@@ -857,7 +857,7 @@ function Settings(): React.JSX.Element {
const generalNavGroups: SettingsNavGroup[] = SETTINGS_NAV_GROUPS.map((group) => ({
...group,
sections: generalNavSections.filter((section) => section.group === group.id)
})).filter((group) => group.sections.length > 0)
})).filter((group) => group.sections.length > 0 || group.id === 'setup')
const repoNavSections = visibleNavSections
.filter((section) => section.id.startsWith('repo-'))
.map((section) => {
@@ -910,8 +910,6 @@ function Settings(): React.JSX.Element {
</div>
) : (
<ActiveSettingsSectionProvider value={activeSectionId}>
{settingsSearchQuery.trim() === '' ? <SettingsSetupGuideCard /> : null}
<SettingsSection
id="agents"
title="Agents"
@@ -982,6 +980,16 @@ function Settings(): React.JSX.Element {
</>
) : null}
<SettingsSection
id="setup-guide"
title="Onboarding checklist"
description="Finish the core workflows that make Orca useful for parallel agent work."
searchEntries={getSectionSearchEntries('setup-guide')}
bodyClassName="overflow-hidden p-0"
>
{isSectionMounted('setup-guide') ? <SettingsSetupGuidePane /> : null}
</SettingsSection>
<SettingsSection
id="general"
title="General"
@@ -1,92 +0,0 @@
import { ArrowRight, ListChecks } from 'lucide-react'
import { useMemo } from 'react'
import {
getFeatureWallSetupSteps,
getFirstIncompleteFeatureWallSetupStepId
} from '../../../../shared/feature-wall-setup-steps'
import type { FeatureWallSetupStepId } from '../../../../shared/feature-wall-setup-steps'
import { useAppStore } from '@/store'
import { Button } from '../ui/button'
import { useSetupGuideProgress } from '../setup-guide/use-setup-guide-progress'
export function SettingsSetupGuideCard(): React.JSX.Element | null {
const openModal = useAppStore((s) => s.openModal)
const progress = useSetupGuideProgress(true, false, false)
const setupSteps = useMemo(() => getFeatureWallSetupSteps(), [])
const unfinishedSteps = useMemo(
() => setupSteps.filter((step) => !progress.stepDone[step.id]),
[progress.stepDone, setupSteps]
)
const completedStepCount = useMemo(
() => setupSteps.filter((step) => progress.stepDone[step.id]).length,
[progress.stepDone, setupSteps]
)
const firstUnfinishedStepId: FeatureWallSetupStepId = getFirstIncompleteFeatureWallSetupStepId(
progress.stepDone
)
const setupComplete = unfinishedSteps.length === 0
return (
<section className="rounded-xl border border-border/60 bg-card/50 px-5 py-4">
<div className="flex items-start justify-between gap-5">
<div className="flex min-w-0 items-start gap-3">
<span className="flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground">
<ListChecks className="size-4" />
</span>
<div className="min-w-0 space-y-3">
<div className="space-y-1">
<h2 className="text-base font-semibold leading-tight text-foreground">
Onboarding checklist
</h2>
<p className="text-sm text-muted-foreground">
{completedStepCount}/{setupSteps.length} setup tasks complete
</p>
</div>
<div className="space-y-2">
<p className="text-[11px] font-medium uppercase tracking-[0.12em] text-muted-foreground">
{setupComplete ? 'Status' : 'To do'}
</p>
<div className="flex flex-wrap gap-2">
{setupComplete ? (
<span className="rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground">
All core workflows complete
</span>
) : (
<>
{unfinishedSteps.slice(0, 3).map((step) => (
<span
key={step.id}
className="rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground"
>
{step.name}
</span>
))}
{unfinishedSteps.length > 3 ? (
<span className="rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground">
+{unfinishedSteps.length - 3} more
</span>
) : null}
</>
)}
</div>
</div>
</div>
</div>
<Button
type="button"
size="sm"
className="w-fit shrink-0 whitespace-nowrap gap-1.5"
onClick={() =>
openModal('setup-guide', {
setupStepId: firstUnfinishedStepId,
telemetrySource: 'settings'
})
}
>
{setupComplete ? 'Review setup' : 'Continue setup'}
<ArrowRight className="size-4" />
</Button>
</div>
</section>
)
}
@@ -0,0 +1,58 @@
import { useEffect, useMemo, useState } from 'react'
import {
getFeatureWallSetupSteps,
getFirstIncompleteFeatureWallSetupStepId
} from '../../../../shared/feature-wall-setup-steps'
import type { FeatureWallSetupStepId } from '../../../../shared/feature-wall-setup-steps'
import { FeatureWallSetupChecklist } from '../feature-wall/FeatureWallSetupChecklist'
import { useSetupGuideProgress } from '../setup-guide/use-setup-guide-progress'
export function SettingsSetupGuidePane(): React.JSX.Element {
const setupSteps = useMemo(() => getFeatureWallSetupSteps(), [])
const [activeStepId, setActiveStepId] = useState<FeatureWallSetupStepId>(
() => setupSteps[0]?.id ?? 'default-agent'
)
const [userSelectedStep, setUserSelectedStep] = useState(false)
const [orchestrationSkillInstalled, setOrchestrationSkillInstalled] = useState(false)
const [browserUseSkillInstalled, setBrowserUseSkillInstalled] = useState(false)
const progress = useSetupGuideProgress(
true,
orchestrationSkillInstalled,
browserUseSkillInstalled
)
const activeStep = setupSteps.find((step) => step.id === activeStepId) ?? setupSteps[0] ?? null
useEffect(() => {
if (userSelectedStep) {
return
}
setActiveStepId(getFirstIncompleteFeatureWallSetupStepId(progress.stepDone))
}, [progress.stepDone, userSelectedStep])
useEffect(() => {
if (!activeStep || userSelectedStep || !progress.stepDone[activeStep.id]) {
return
}
const nextUnfinishedStepId = getFirstIncompleteFeatureWallSetupStepId(progress.stepDone)
if (nextUnfinishedStepId !== activeStep.id) {
setActiveStepId(nextUnfinishedStepId)
}
}, [activeStep, progress.stepDone, userSelectedStep])
const handleSelectStep = (id: FeatureWallSetupStepId): void => {
setUserSelectedStep(true)
setActiveStepId(id)
}
return (
<div className="h-[min(740px,calc(100vh-14rem))] min-h-[540px] p-5">
<FeatureWallSetupChecklist
activeStep={activeStep}
progress={progress}
onSelectStep={handleSelectStep}
onOrchestrationSkillInstalledChange={setOrchestrationSkillInstalled}
onBrowserUseSkillInstalledChange={setBrowserUseSkillInstalled}
/>
</div>
)
}
@@ -2,6 +2,7 @@ import { renderToStaticMarkup } from 'react-dom/server'
import { Bot, Mic, Network } from 'lucide-react'
import { describe, expect, it, vi } from 'vitest'
import { SettingsSidebar } from './SettingsSidebar'
import { TooltipProvider } from '../ui/tooltip'
vi.mock('@/hooks/useShortcutLabel', () => ({
useShortcutLabel: () => '⌘F'
@@ -9,52 +10,54 @@ vi.mock('@/hooks/useShortcutLabel', () => ({
function renderSidebar(): string {
return renderToStaticMarkup(
<SettingsSidebar
activeSectionId="orchestration"
generalGroups={[
{
id: 'capabilities',
title: 'AI Capabilities',
sections: [
{
id: 'agents',
title: 'Agents',
icon: Bot
},
{
id: 'orchestration',
title: 'Orchestration',
icon: Network,
installStatus: 'install'
},
{
id: 'voice',
title: 'Voice',
icon: Mic,
installStatus: 'installed'
}
]
},
{
id: 'setup',
title: 'Set Up',
sections: [
{
id: 'accounts',
title: 'AI Provider Accounts',
icon: Bot,
badge: 'Optional'
}
]
}
]}
repoSections={[]}
hasRepos={false}
searchQuery=""
onBack={vi.fn()}
onSearchChange={vi.fn()}
onSelectSection={vi.fn()}
/>
<TooltipProvider>
<SettingsSidebar
activeSectionId="orchestration"
generalGroups={[
{
id: 'capabilities',
title: 'AI Capabilities',
sections: [
{
id: 'agents',
title: 'Agents',
icon: Bot
},
{
id: 'orchestration',
title: 'Orchestration',
icon: Network,
installStatus: 'install'
},
{
id: 'voice',
title: 'Voice',
icon: Mic,
installStatus: 'installed'
}
]
},
{
id: 'setup',
title: 'Set Up',
sections: [
{
id: 'accounts',
title: 'AI Provider Accounts',
icon: Bot,
badge: 'Optional'
}
]
}
]}
repoSections={[]}
hasRepos={false}
searchQuery=""
onBack={vi.fn()}
onSearchChange={vi.fn()}
onSelectSection={vi.fn()}
/>
</TooltipProvider>
)
}
@@ -1,17 +1,21 @@
import type { RefObject } from 'react'
import { ArrowLeft, Search, Server, type LucideIcon, type LucideProps } from 'lucide-react'
import { ArrowLeft, Search, Server } from 'lucide-react'
import logo from '../../../../../resources/logo.svg'
import type { RepoIcon } from '../../../../shared/repo-icon'
import type { SettingsNavInstallStatus } from '@/lib/settings-navigation-types'
import type { SettingsNavIcon, SettingsNavInstallStatus } from '@/lib/settings-navigation-types'
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
import { cn } from '@/lib/utils'
import { RepoIconGlyph } from '../repo/repo-icon'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
import { SetupGuideProgressRing } from '../setup-guide/SetupGuideProgressRing'
import { useSettingsSetupGuideProgress } from './settings-setup-guide-progress'
import type { SettingsSetupGuideProgress } from './settings-setup-guide-progress'
type NavSection = {
id: string
title: string
icon: LucideIcon | ((props: LucideProps) => React.JSX.Element)
icon: SettingsNavIcon
badge?: string
installStatus?: SettingsNavInstallStatus
}
@@ -48,6 +52,64 @@ type SettingsSidebarProps = {
) => void
}
type SettingsSetupGuideRowProps = {
progress: SettingsSetupGuideProgress
setupActive: boolean
onSelect: (modifiers: {
metaKey: boolean
ctrlKey: boolean
shiftKey: boolean
altKey: boolean
}) => void
}
function SettingsSetupGuideNavRow({
progress,
setupActive,
onSelect
}: SettingsSetupGuideRowProps): React.JSX.Element {
return (
<button
type="button"
aria-current={setupActive ? 'page' : undefined}
aria-label={`Onboarding checklist, ${progress.doneCount} of ${progress.total} done. Show setup guide.`}
onClick={(event) =>
onSelect({
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey
})
}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50',
setupActive
? 'bg-accent font-medium text-accent-foreground'
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
)}
>
<img
src={logo}
alt=""
aria-hidden="true"
className={cn(
'size-4 shrink-0 object-contain invert dark:invert-0',
setupActive ? 'opacity-75' : 'opacity-45'
)}
/>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-[13px] font-medium leading-4">Onboarding checklist</span>
</span>
<SetupGuideProgressRing
done={progress.doneCount}
total={progress.total}
className="ml-auto shrink-0"
tooltipLabel={`${progress.doneCount}/${progress.total} complete`}
/>
</button>
)
}
export function SettingsSidebar({
activeSectionId,
generalGroups,
@@ -59,6 +121,9 @@ export function SettingsSidebar({
onSearchChange,
onSelectSection
}: SettingsSidebarProps): React.JSX.Element {
const setupGuideProgress = useSettingsSetupGuideProgress(true)
const setupActive = activeSectionId === 'setup-guide'
const showSetupGuideTopRow = setupGuideProgress.doneCount < setupGuideProgress.total
const searchShortcutHint = useShortcutLabel('settings.search')
const navItemClassName = (isActive: boolean): string =>
cn(
@@ -119,6 +184,16 @@ export function SettingsSidebar({
</div>
</div>
{showSetupGuideTopRow ? (
<div className="border-b border-border/50 px-3 py-3">
<SettingsSetupGuideNavRow
progress={setupGuideProgress}
setupActive={setupActive}
onSelect={(modifiers) => onSelectSection('setup-guide', modifiers)}
/>
</div>
) : null}
<div className="min-h-0 flex-1 overflow-y-auto scrollbar-sleek px-3 py-4">
<div className="space-y-5">
{generalGroups.map((group) => (
@@ -127,9 +202,11 @@ export function SettingsSidebar({
{group.title}
</p>
<div className="space-y-1">
{group.sections.map((section) => {
const Icon = section.icon
const isActive = activeSectionId === section.id
{group.sections
.filter((section) => section.id !== 'setup-guide')
.map((section) => {
const Icon = section.icon
const isActive = activeSectionId === section.id
return (
<button
@@ -0,0 +1,61 @@
import { describe, expect, it } from 'vitest'
import type { FeatureWallSetupStepId } from '../../../../shared/feature-wall-setup-steps'
import {
getSettingsSetupGuideProgress,
SETTINGS_SETUP_GUIDE_STEP_IDS
} from './settings-setup-guide-progress'
describe('settings setup guide progress', () => {
it('tracks the five-step settings checklist', () => {
expect(SETTINGS_SETUP_GUIDE_STEP_IDS).toEqual([
'split-terminal',
'two-worktrees',
'notifications',
'default-agent',
'task-sources'
])
})
it('returns a 3/5 progress label source and first incomplete step', () => {
const stepDone = {
'split-terminal': true,
'two-worktrees': true,
notifications: true
} satisfies Partial<Record<FeatureWallSetupStepId, boolean>>
expect(getSettingsSetupGuideProgress(stepDone)).toEqual({
doneCount: 3,
total: 5,
firstIncompleteStepId: 'default-agent'
})
})
it('ignores setup-guide tasks that are not part of the settings checklist', () => {
const stepDone = {
'split-terminal': true,
'two-worktrees': true,
notifications: true,
'setup-script': true,
'add-two-repos': true,
'agent-capabilities': true
} satisfies Partial<Record<FeatureWallSetupStepId, boolean>>
expect(getSettingsSetupGuideProgress(stepDone)).toEqual({
doneCount: 3,
total: 5,
firstIncompleteStepId: 'default-agent'
})
})
it('marks the settings checklist complete when all five settings steps are done', () => {
const stepDone = Object.fromEntries(
SETTINGS_SETUP_GUIDE_STEP_IDS.map((stepId) => [stepId, true])
) as Record<FeatureWallSetupStepId, boolean>
expect(getSettingsSetupGuideProgress(stepDone)).toEqual({
doneCount: 5,
total: 5,
firstIncompleteStepId: null
})
})
})
@@ -0,0 +1,98 @@
import { useEffect, useMemo } from 'react'
import type { FeatureWallSetupStepId } from '../../../../shared/feature-wall-setup-steps'
import { useAppStore } from '@/store'
import { getFeatureWallSetupProgress } from '../feature-wall/feature-wall-setup-progress'
export const SETTINGS_SETUP_GUIDE_STEP_IDS = [
'split-terminal',
'two-worktrees',
'notifications',
'default-agent',
'task-sources'
] as const satisfies readonly FeatureWallSetupStepId[]
export type SettingsSetupGuideProgress = {
doneCount: number
total: number
firstIncompleteStepId: FeatureWallSetupStepId | null
}
export function getSettingsSetupGuideProgress(
stepDone: Partial<Record<FeatureWallSetupStepId, boolean>>
): SettingsSetupGuideProgress {
const doneCount = SETTINGS_SETUP_GUIDE_STEP_IDS.filter((stepId) => stepDone[stepId]).length
const firstIncompleteStepId =
SETTINGS_SETUP_GUIDE_STEP_IDS.find((stepId) => !stepDone[stepId]) ?? null
return {
doneCount,
total: SETTINGS_SETUP_GUIDE_STEP_IDS.length,
firstIncompleteStepId
}
}
export function useSettingsSetupGuideProgress(
shouldRefreshTaskSourceState: boolean
): SettingsSetupGuideProgress {
const settings = useAppStore((s) => s.settings)
const featureInteractions = useAppStore((s) => s.featureInteractions)
const worktreesByRepo = useAppStore((s) => s.worktreesByRepo)
const tabsByWorktree = useAppStore((s) => s.tabsByWorktree)
const terminalLayoutsByTabId = useAppStore((s) => s.terminalLayoutsByTabId)
const preflightStatus = useAppStore((s) => s.preflightStatus)
const preflightStatusChecked = useAppStore((s) => s.preflightStatusChecked)
const refreshPreflightStatus = useAppStore((s) => s.refreshPreflightStatus)
const linearStatus = useAppStore((s) => s.linearStatus)
const linearStatusChecked = useAppStore((s) => s.linearStatusChecked)
const checkLinearConnection = useAppStore((s) => s.checkLinearConnection)
useEffect(() => {
if (!shouldRefreshTaskSourceState) {
return
}
if (!preflightStatusChecked) {
void refreshPreflightStatus()
}
if (!linearStatusChecked) {
void checkLinearConnection()
}
}, [
checkLinearConnection,
linearStatusChecked,
preflightStatusChecked,
refreshPreflightStatus,
shouldRefreshTaskSourceState
])
const hasConnectedTaskSource =
(preflightStatus?.gh.installed === true && preflightStatus.gh.authenticated === true) ||
(preflightStatus?.glab?.installed === true && preflightStatus.glab.authenticated === true) ||
linearStatus.connected === true
return useMemo(() => {
// Why: Settings renders only five steps, so avoid the full setup guide
// probes for setup scripts and agent skills, especially over SSH.
const fullProgress = getFeatureWallSetupProgress({
settings,
featureInteractions,
hasConnectedTaskSource,
browserUseSkillInstalled: false,
computerUseSkillInstalled: false,
computerUsePermissionsReady: false,
orchestrationSkillInstalled: false,
gitRepoCount: 0,
worktreesByRepo,
tabsByWorktree,
terminalLayoutsByTabId,
hasSetupScript: false
})
return getSettingsSetupGuideProgress(fullProgress.stepDone)
}, [
featureInteractions,
hasConnectedTaskSource,
settings,
tabsByWorktree,
terminalLayoutsByTabId,
worktreesByRepo
])
}
@@ -8,6 +8,7 @@ type SetupGuideProgressRingProps = {
className?: string
sizeClassName?: string
strokeWidth?: number
tooltipLabel?: string
}
export function SetupGuideProgressRing({
@@ -15,7 +16,8 @@ export function SetupGuideProgressRing({
total,
className,
sizeClassName = 'size-5',
strokeWidth = 2
strokeWidth = 2,
tooltipLabel
}: SetupGuideProgressRingProps): JSX.Element {
const boundedTotal = Math.max(total, 1)
const boundedDone = Math.min(Math.max(done, 0), boundedTotal)
@@ -62,7 +64,7 @@ export function SetupGuideProgressRing({
</span>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}>
{progressLabel}
{tooltipLabel ?? progressLabel}
</TooltipContent>
</Tooltip>
)
@@ -6,13 +6,13 @@ import {
ExternalLink,
FolderPlus,
HardDrive,
ListChecks,
MessageSquareText,
RotateCw,
School,
Settings,
Smartphone
} from 'lucide-react'
import logo from '../../../../../resources/logo.svg'
import { useAppStore } from '@/store'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
@@ -167,7 +167,12 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
<DropdownMenuItem
onSelect={() => openModal('setup-guide', { telemetrySource: 'help_menu' })}
>
<ListChecks className="size-3.5" />
<img
src={logo}
alt=""
aria-hidden="true"
className="size-3.5 object-contain invert opacity-55 dark:invert-0"
/>
Onboarding checklist
</DropdownMenuItem>
<DropdownMenuItem
@@ -24,12 +24,13 @@ function ids(args: { isMac?: boolean; isWindows?: boolean; isWebClient?: boolean
describe('settings navigation metadata', () => {
it('puts AI capability panes at the top on desktop', () => {
expect(ids().slice(0, 8)).toEqual([
expect(ids().slice(0, 9)).toEqual([
'agents',
'accounts',
'orchestration',
'computer-use',
'voice',
'setup-guide',
'general',
'integrations',
'git'
@@ -37,10 +38,11 @@ describe('settings navigation metadata', () => {
})
it('puts web-safe AI capability panes at the top while hiding desktop-only panes', () => {
expect(ids({ isWebClient: true }).slice(0, 6)).toEqual([
expect(ids({ isWebClient: true }).slice(0, 7)).toEqual([
'agents',
'accounts',
'orchestration',
'setup-guide',
'general',
'integrations',
'git'
@@ -1,5 +1,5 @@
/* oxlint-disable max-lines */
import { useMemo } from 'react'
import { createElement, useMemo } from 'react'
// Why: this registry mirrors the Settings sidebar in one neutral module so
// Cmd+J and Settings visibility cannot drift. Keep it free of Settings pane UI
// imports; the boundary is enforced by a focused architecture test.
@@ -30,6 +30,8 @@ import {
UserCog,
Wrench
} from 'lucide-react'
import type { LucideProps } from 'lucide-react'
import logo from '../../../../resources/logo.svg'
import type { Repo } from '../../../shared/types'
import { getRepoKindLabel } from '../../../shared/repo-kind'
import { useAppStore } from '@/store'
@@ -65,6 +67,16 @@ import { SHORTCUTS_PANE_SEARCH_ENTRIES } from '@/components/settings/shortcuts-s
import { STATS_PANE_SEARCH_ENTRIES } from '@/components/stats/stats-search'
import { EXPERIMENTAL_PANE_SEARCH_ENTRIES } from '@/components/settings/experimental-search'
import { getRepositoryPaneSearchEntries } from '@/components/settings/repository-search'
import { cn } from '@/lib/utils'
function OrcaLogoSettingsIcon({ className }: LucideProps) {
return createElement('img', {
src: logo,
alt: '',
'aria-hidden': true,
className: cn('object-contain invert dark:invert-0', className)
})
}
export function isWebClientLocation(): boolean {
return (
@@ -142,6 +154,20 @@ export function buildSettingsNavigationMetadata({
}
]
: []),
{
id: 'setup-guide',
title: 'Onboarding checklist',
description: 'Finish the onboarding checklist for core Orca workflows.',
icon: OrcaLogoSettingsIcon,
searchEntries: [
{
title: 'Onboarding checklist',
description: 'Open the onboarding checklist for setup and milestone steps.',
keywords: ['setup guide', 'get started with Orca', 'getting started']
}
],
group: 'setup'
},
{
id: 'general',
title: 'General',
@@ -1,6 +1,8 @@
import type { LucideIcon } from 'lucide-react'
import type { ComponentType } from 'react'
import type { LucideProps } from 'lucide-react'
import type { SettingsSearchEntry } from '@/components/settings/settings-search'
export type SettingsNavIcon = ComponentType<LucideProps>
export type SettingsNavInstallStatus = 'install' | 'installed' | 'checking'
export type SettingsNavTarget =
@@ -35,7 +37,7 @@ export type SettingsNavSection = {
id: string
title: string
description: string
icon: LucideIcon
icon: SettingsNavIcon
searchEntries: SettingsSearchEntry[]
group: string
badge?: string