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(() => {