mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
Improve startup with lazy dialog loading and resilient CLI resolution
- Defer SkillFreshnessUpdateDialog and LinearAgentSkillSetupDialog load until first request, removing ~120ms of module parsing from critical path - Keep dialogs mounted after first open to allow Radix exit animation, fixing early unmount that was cutting off close transitions - Gracefully degrade CLI resolution when nvm directory is unreadable (stale mount, dangling symlink), falling back to PATH instead of aborting command detection
This commit is contained in:
@@ -327,6 +327,25 @@ describe('resolveCliCommands', () => {
|
||||
expect(resolved.get('codex')).toBe(join(pathDir, 'codex'))
|
||||
})
|
||||
|
||||
it('still resolves PATH commands when the nvm versions dir is unreadable', async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-cli-commands-'))
|
||||
const pathDir = join(root, 'bin')
|
||||
const pathClaude = join(pathDir, 'claude')
|
||||
makeExecutable(pathClaude)
|
||||
// Why: a file where a directory is expected makes readdir throw ENOTDIR,
|
||||
// standing in for stale-mount/EACCES failures on ~/.nvm/versions/node.
|
||||
makeNonExecutableFile(join(root, '.nvm', 'versions', 'node'))
|
||||
|
||||
const resolved = await resolveCliCommands(['claude', 'missing'], {
|
||||
platform: 'darwin',
|
||||
pathEnv: pathDir,
|
||||
homePath: root
|
||||
})
|
||||
|
||||
expect(resolved.get('claude')).toBe(pathClaude)
|
||||
expect(resolved.get('missing')).toBe('missing')
|
||||
})
|
||||
|
||||
it('keeps Linux executable matching case-sensitive', async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-cli-commands-'))
|
||||
const pathDir = join(root, 'bin')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { accessSync, constants, existsSync, readdirSync, statSync } from 'node:fs'
|
||||
import { accessSync, constants, existsSync, readdirSync, statSync, type Dirent } from 'node:fs'
|
||||
import { access, readdir, stat } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { delimiter, dirname, join } from 'node:path'
|
||||
@@ -214,7 +214,17 @@ function getNvmVersionDirectories(homePath: string): string[] {
|
||||
return []
|
||||
}
|
||||
|
||||
return readdirSync(nvmVersionsDir, { withFileTypes: true })
|
||||
// Why: bulk detection folds PATH and install dirs into one pass, so an
|
||||
// unreadable nvm dir (stale mount, dangling symlink) must degrade to "no
|
||||
// nvm dirs" instead of aborting resolution and discarding PATH-found CLIs.
|
||||
let entries: Dirent[]
|
||||
try {
|
||||
entries = readdirSync(nvmVersionsDir, { withFileTypes: true })
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
return entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name)
|
||||
.sort(compareVersionDesc)
|
||||
|
||||
@@ -326,7 +326,14 @@ function SkillFreshnessUpdateDialogGate(): React.JSX.Element | null {
|
||||
getSkillFreshnessUpdateDialogRequest,
|
||||
getSkillFreshnessUpdateDialogRequest
|
||||
)
|
||||
if (!requested) {
|
||||
// Why: stay mounted after the first request — closing consumes the request
|
||||
// in the same commit `open` flips false, and unmounting then would cut off
|
||||
// Radix's exit animation.
|
||||
const everRequestedRef = useRef(false)
|
||||
if (requested) {
|
||||
everRequestedRef.current = true
|
||||
}
|
||||
if (!everRequestedRef.current) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -238,7 +238,7 @@ describe('renderer startup runtime routing', () => {
|
||||
|
||||
expect(source).toContain("import('./LinearAgentSkillSetupDialog').then")
|
||||
expect(source).not.toContain("from './LinearAgentSkillSetupDialog'")
|
||||
expect(source).toContain('const setupDialog = setupDialogOpen ?')
|
||||
expect(source).toContain('const setupDialog = setupDialogEverOpenedRef.current ?')
|
||||
})
|
||||
|
||||
it('loads worktree note Markdown only with the hover detail surface', () => {
|
||||
|
||||
@@ -86,6 +86,12 @@ export function LinearAgentSkillSetupPrompt({
|
||||
const [cliStatus, setCliStatus] = useState<CliInstallStatus | null>(null)
|
||||
const [cliLoading, setCliLoading] = useState(linked)
|
||||
const [setupDialogOpen, setSetupDialogOpen] = useState(false)
|
||||
// Why: keep the dialog mounted once opened so closing can play Radix's exit
|
||||
// animation; only the very first mount is deferred off the startup path.
|
||||
const setupDialogEverOpenedRef = useRef(false)
|
||||
if (setupDialogOpen) {
|
||||
setupDialogEverOpenedRef.current = true
|
||||
}
|
||||
const [setupCheckResult, setSetupCheckResult] = useState<SetupCheckResult>('idle')
|
||||
const [activeSetupCheckIdentity, setActiveSetupCheckIdentity] = useState<string | null>(null)
|
||||
const agentRuntime = useMemo(
|
||||
@@ -287,8 +293,9 @@ export function LinearAgentSkillSetupPrompt({
|
||||
}
|
||||
|
||||
// Why: rendering a lazy component with open=false still fetches its module.
|
||||
// Gate the element itself so the inline installer terminal stays off startup.
|
||||
const setupDialog = setupDialogOpen ? (
|
||||
// Gate the element until the first open so the inline installer terminal
|
||||
// stays off startup.
|
||||
const setupDialog = setupDialogEverOpenedRef.current ? (
|
||||
<Suspense fallback={null}>
|
||||
<LinearAgentSkillSetupDialog
|
||||
open={setupDialogOpen}
|
||||
|
||||
@@ -519,6 +519,11 @@ export function ensureStartupBenchmarkFixture(fixtureDir, options) {
|
||||
['Service Worker', 'CacheStorage'],
|
||||
['terminal-scrollback-snapshots']
|
||||
]
|
||||
// Why: rebuilding with a smaller file count must not inherit higher-index
|
||||
// files from a prior larger fixture, or the icacls walk measures the old size.
|
||||
for (const bucketRoot of new Set(buckets.map(([root]) => root))) {
|
||||
rmSync(join(fixtureDir, bucketRoot), { recursive: true, force: true })
|
||||
}
|
||||
const payload = 'x'.repeat(1024)
|
||||
let written = 0
|
||||
const started = Date.now()
|
||||
|
||||
Reference in New Issue
Block a user