diff --git a/src/components/CommandPalette.test.tsx b/src/components/CommandPalette.test.tsx
index 263686be..f99a4ae0 100644
--- a/src/components/CommandPalette.test.tsx
+++ b/src/components/CommandPalette.test.tsx
@@ -235,6 +235,16 @@ describe('CommandPalette', () => {
expect(screen.queryByText('Search Notes')).not.toBeInTheDocument()
})
+ it('focuses the AI editor immediately when the leading space triggers AI mode', () => {
+ render()
+
+ const input = screen.getByPlaceholderText('Type a command...')
+ input.focus()
+ fireEvent.change(input, { target: { value: ' ' } })
+
+ expect(screen.getByTestId('command-palette-ai-input')).toHaveFocus()
+ })
+
it('returns to command mode when the leading space is deleted', () => {
render(
,
diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx
index 58702f0f..ae0d2d8a 100644
--- a/src/components/CommandPalette.tsx
+++ b/src/components/CommandPalette.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, useState } from 'react'
+import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import type { VaultEntry } from '../types'
import { fuzzyMatch } from '../utils/fuzzyMatch'
@@ -205,15 +205,17 @@ function OpenCommandPalette({
const aiMode = aiValue.startsWith(' ')
const { groups, flatList } = usePaletteResults(commands, query)
- useEffect(() => {
- const focusTimer = window.setTimeout(() => {
- if (aiMode) {
- aiInputRef.current?.focus()
- return
- }
- inputRef.current?.focus()
- }, 50)
- return () => window.clearTimeout(focusTimer)
+ useLayoutEffect(() => {
+ const target = aiMode ? aiInputRef.current : inputRef.current
+ if (!target) return
+
+ target.focus()
+ if (document.activeElement === target) return
+
+ const focusRetry = window.requestAnimationFrame(() => {
+ target.focus()
+ })
+ return () => window.cancelAnimationFrame(focusRetry)
}, [aiMode])
useEffect(() => {