@@ -68,8 +68,9 @@ type SearchAutocompleteListProps = {
6868 /** Whether to subscribe to KeyboardShortcut arrow keys events */
6969 shouldSubscribeToArrowKeyEvents ?: boolean ;
7070
71- /** Callback to highlight (e.g. scroll to) the first matched item in the list. */
72- onHighlightFirstItem ?: ( ) => void ;
71+ /** Whether to highlight the first matched result so Enter selects it. Only the SearchRouter (Cmd+K) uses this;
72+ * the search page input keeps focus on the search-query row to match production behavior. */
73+ shouldHighlightFirstItem ?: boolean ;
7374
7475 /** Ref for the external text input */
7576 textInputRef ?: RefObject < AnimatedTextInputRef | null > ;
@@ -145,7 +146,7 @@ function SearchAutocompleteList({
145146 getAdditionalSections,
146147 onListItemPress,
147148 shouldSubscribeToArrowKeyEvents = true ,
148- onHighlightFirstItem ,
149+ shouldHighlightFirstItem = false ,
149150 textInputRef,
150151 autocompleteSubstitutions,
151152 ref,
@@ -273,8 +274,8 @@ function SearchAutocompleteList({
273274 // effect can re-fire and correctly focus the first focusable item (skipping section headers).
274275 hasSetInitialFocusRef . current = false ;
275276 } else {
276- // When query changes to a non-empty value, focus on the search query item (index 0) and scroll to top
277- // onHighlightFirstItem will switch focus to the first result when there's a good match
277+ // When query changes to a non-empty value, focus on the search query item (index 0) and scroll to top.
278+ // The highlight effect below switches focus to the first result when there's a good match.
278279 innerListRef . current ?. updateAndScrollToFocusedIndex ( 0 , true ) ;
279280 }
280281 }
@@ -570,8 +571,6 @@ function SearchAutocompleteList({
570571 reports ,
571572 ] ) ;
572573
573- const sectionItemText = sections ?. at ( 1 ) ?. data ?. [ 0 ] ?. text ?? '' ;
574- const normalizedReferenceText = sectionItemText . toLowerCase ( ) ;
575574 const trimmedAutocompleteQueryValue = autocompleteQueryValue . trim ( ) ;
576575 const isLoading = ! isRecentSearchesDataLoaded ;
577576 const suggestionsAnnouncement = suggestionsCount > 0 ? translate ( 'search.suggestionsAvailable' , { count : suggestionsCount } , trimmedAutocompleteQueryValue ) : '' ;
@@ -581,29 +580,37 @@ function SearchAutocompleteList({
581580 const shouldAnnounceNoResults = ! isLoading && suggestionsCount === 0 && ! ! trimmedAutocompleteQueryValue ;
582581 useDebouncedAccessibilityAnnouncement ( noResultsFoundText , shouldAnnounceNoResults , autocompleteQueryValue ) ;
583582
584- const firstRecentReportKey = styledRecentReports . at ( 0 ) ?. keyForList ;
583+ // Locate the first recent report row in the order it is actually rendered. The two-section switcher sorts the
584+ // local "Recent chats" rows by a frozen rank, so the rendered order can differ from styledRecentReports (the
585+ // unsorted combined local + server list). Walking sections keeps the focused row, its reference text, and the
586+ // initially focused key all pointing at the first row the user actually sees.
587+ const recentReportKeys = new Set ( styledRecentReports . map ( ( report ) => report . keyForList ) ) ;
588+ let firstRecentReportKey : string | undefined ;
589+ let firstRecentReportText = '' ;
585590 let firstRecentReportFlatIndex = - 1 ;
586- if ( firstRecentReportKey ) {
587- let flatIndex = 0 ;
588- for ( const section of sections ) {
589- const hasData = ( section . data ?. length ?? 0 ) > 0 ;
590- const hasHeader = hasData && ( section . title !== undefined || ( 'customHeader' in section && section . customHeader !== undefined ) ) ;
591- if ( hasHeader ) {
592- flatIndex ++ ;
593- }
594- for ( const item of section . data ?? [ ] ) {
595- if ( item . keyForList === firstRecentReportKey ) {
596- firstRecentReportFlatIndex = flatIndex ;
597- break ;
598- }
599- flatIndex ++ ;
600- }
601- if ( firstRecentReportFlatIndex !== - 1 ) {
591+ let flatIndex = 0 ;
592+ for ( const section of sections ) {
593+ const hasData = ( section . data ?. length ?? 0 ) > 0 ;
594+ const hasHeader = hasData && ( section . title !== undefined || ( 'customHeader' in section && section . customHeader !== undefined ) ) ;
595+ if ( hasHeader ) {
596+ flatIndex ++ ;
597+ }
598+ for ( const item of section . data ?? [ ] ) {
599+ if ( item . keyForList && recentReportKeys . has ( item . keyForList ) ) {
600+ firstRecentReportKey = item . keyForList ;
601+ firstRecentReportText = item . text ?? '' ;
602+ firstRecentReportFlatIndex = flatIndex ;
602603 break ;
603604 }
605+ flatIndex ++ ;
606+ }
607+ if ( firstRecentReportFlatIndex !== - 1 ) {
608+ break ;
604609 }
605610 }
606611
612+ const normalizedReferenceText = firstRecentReportText . toLowerCase ( ) ;
613+
607614 // When options initialize after the list is already mounted, initiallyFocusedItemKey has no effect
608615 // because useState(initialFocusedIndex) in useArrowKeyFocusManager only reads the initial value.
609616 // Imperatively focus the first recent report once options become available (desktop only).
@@ -619,10 +626,13 @@ function SearchAutocompleteList({
619626 useEffect ( ( ) => {
620627 const targetText = autocompleteQueryValue ;
621628
622- if ( shouldHighlight ( normalizedReferenceText , targetText ) ) {
623- onHighlightFirstItem ?. ( ) ;
629+ if ( ! shouldHighlightFirstItem || firstRecentReportFlatIndex === - 1 || ! shouldHighlight ( normalizedReferenceText , targetText ) ) {
630+ return ;
624631 }
625- } , [ autocompleteQueryValue , onHighlightFirstItem , normalizedReferenceText ] ) ;
632+ // Focus the header-aware flat index of the first result. A fixed index (e.g. searchQueryItems.length)
633+ // lands on the "Recent chats" section header row after the two-section switcher was introduced.
634+ innerListRef . current ?. updateAndScrollToFocusedIndex ( firstRecentReportFlatIndex , true ) ;
635+ } , [ autocompleteQueryValue , firstRecentReportFlatIndex , normalizedReferenceText , shouldHighlightFirstItem ] ) ;
626636
627637 if ( isLoading ) {
628638 return (
0 commit comments