diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 34333cfd9d577..561f6f6ed35f2 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1488,6 +1488,8 @@ const CONST = { SKELETON_ANIMATION_SPEED: 3, SEARCH_OPTIONS_COMPARISON: 'search_options_comparison', SEARCH_MOST_RECENT_OPTIONS: 'search_most_recent_options', + DEBOUNCE_HANDLE_SEARCH: 'debounce_handle_search', + FAST_SEARCH_TREE_CREATION: 'fast_search_tree_creation', }, PRIORITY_MODE: { GSD: 'gsd', diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index cbc61c4be97e9..44a9510742835 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -18,6 +18,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {getCardFeedKey, getCardFeedNamesWithType} from '@libs/CardFeedUtils'; import {getCardDescription, isCard, isCardHiddenFromSearch, mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; +import Log from '@libs/Log'; import memoize from '@libs/memoize'; import type {Options, SearchOption} from '@libs/OptionsListUtils'; import {combineOrderingOfReportsAndPersonalDetails, getSearchOptions, getValidPersonalDetailOptions, optionsOrderBy, recentReportComparator} from '@libs/OptionsListUtils'; @@ -497,33 +498,124 @@ function SearchAutocompleteList( const {search: filterOptions, isInitialized: isFastSearchInitialized} = useFastSearchFromOptions(searchOptions, {includeUserToInvite: true}); const recentReportsOptions = useMemo(() => { - Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); - if (autocompleteQueryValue.trim() === '' || !isFastSearchInitialized) { - const orderedReportOptions = optionsOrderBy(searchOptions.recentReports, 20, recentReportComparator); - Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); - return orderedReportOptions; - } + const actionId = `filter_options_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + const startTime = Date.now(); - const filteredOptions = filterOptions(autocompleteQueryValue); - const orderedOptions = combineOrderingOfReportsAndPersonalDetails(filteredOptions, autocompleteQueryValue, { - sortByReportTypeInSearch: true, - preferChatRoomsOverThreads: true, + Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Performance.markStart(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Log.info('[CMD_K_DEBUG] Filter options started', false, { + actionId, + queryLength: autocompleteQueryValue.length, + queryTrimmed: autocompleteQueryValue.trim(), + isFastSearchInitialized, + recentReportsCount: searchOptions.recentReports.length, + timestamp: startTime, }); - const reportOptions: OptionData[] = [...orderedOptions.recentReports, ...orderedOptions.personalDetails]; - if (filteredOptions.userToInvite) { - reportOptions.push(filteredOptions.userToInvite); + try { + if (autocompleteQueryValue.trim() === '' || !isFastSearchInitialized) { + const orderedReportOptions = optionsOrderBy(searchOptions.recentReports, 20, recentReportComparator); + + const endTime = Date.now(); + Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Performance.markEnd(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Log.info('[CMD_K_DEBUG] Filter options completed (empty query path)', false, { + actionId, + duration: endTime - startTime, + resultCount: orderedReportOptions.length, + timestamp: endTime, + }); + + return orderedReportOptions; + } + + const filteredOptions = filterOptions(autocompleteQueryValue); + const orderedOptions = combineOrderingOfReportsAndPersonalDetails(filteredOptions, autocompleteQueryValue, { + sortByReportTypeInSearch: true, + preferChatRoomsOverThreads: true, + }); + + const reportOptions: OptionData[] = [...orderedOptions.recentReports, ...orderedOptions.personalDetails]; + if (filteredOptions.userToInvite) { + reportOptions.push(filteredOptions.userToInvite); + } + + const finalOptions = reportOptions.slice(0, 20); + const endTime = Date.now(); + Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Performance.markEnd(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Log.info('[CMD_K_DEBUG] Filter options completed (search path)', false, { + actionId, + duration: endTime - startTime, + recentReportsFiltered: orderedOptions.recentReports.length, + personalDetailsFiltered: orderedOptions.personalDetails.length, + hasUserToInvite: !!filteredOptions.userToInvite, + finalResultCount: finalOptions.length, + timestamp: endTime, + }); + + return finalOptions; + } catch (error) { + const endTime = Date.now(); + Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Performance.markEnd(CONST.TIMING.SEARCH_FILTER_OPTIONS); + Log.alert('[CMD_K_FREEZE] Filter options failed', { + actionId, + error: String(error), + duration: endTime - startTime, + queryLength: autocompleteQueryValue.length, + timestamp: endTime, + }); + throw error; } - Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); - return reportOptions.slice(0, 20); }, [autocompleteQueryValue, filterOptions, searchOptions, isFastSearchInitialized]); const debounceHandleSearch = useDebounce( useCallback(() => { - if (!handleSearch || !autocompleteQueryWithoutFilters) { - return; + const actionId = `debounce_search_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + const startTime = Date.now(); + + Performance.markStart(CONST.TIMING.DEBOUNCE_HANDLE_SEARCH); + Log.info('[CMD_K_DEBUG] Debounced search started', false, { + actionId, + queryLength: autocompleteQueryWithoutFilters?.length ?? 0, + hasHandleSearch: !!handleSearch, + timestamp: startTime, + }); + + try { + if (!handleSearch || !autocompleteQueryWithoutFilters) { + Log.info('[CMD_K_DEBUG] Debounced search skipped - missing dependencies', false, { + actionId, + hasHandleSearch: !!handleSearch, + hasQuery: !!autocompleteQueryWithoutFilters, + timestamp: Date.now(), + }); + return; + } + + handleSearch(autocompleteQueryWithoutFilters); + + const endTime = Date.now(); + Performance.markEnd(CONST.TIMING.DEBOUNCE_HANDLE_SEARCH); + Log.info('[CMD_K_DEBUG] Debounced search completed', false, { + actionId, + duration: endTime - startTime, + queryLength: autocompleteQueryWithoutFilters.length, + timestamp: endTime, + }); + } catch (error) { + const endTime = Date.now(); + Performance.markEnd(CONST.TIMING.DEBOUNCE_HANDLE_SEARCH); + Log.alert('[CMD_K_FREEZE] Debounced search failed', { + actionId, + error: String(error), + duration: endTime - startTime, + queryLength: autocompleteQueryWithoutFilters?.length ?? 0, + timestamp: endTime, + }); + throw error; } - handleSearch(autocompleteQueryWithoutFilters); }, [handleSearch, autocompleteQueryWithoutFilters]), CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME, ); diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index c35716498302b..268bd554c3353 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -28,6 +28,7 @@ import {navigateToAndOpenReport} from '@libs/actions/Report'; import {clearAllFilters} from '@libs/actions/Search'; import {getCardFeedNamesWithType} from '@libs/CardFeedUtils'; import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; +import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; @@ -149,20 +150,51 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo ); const onSearchQueryChange = useCallback( (userQuery: string) => { - const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true); - const updatedUserQuery = getAutocompleteQueryWithComma(textInputValue, singleLineUserQuery); - setTextInputValue(updatedUserQuery); - setAutocompleteQueryValue(updatedUserQuery); - - const updatedSubstitutionsMap = getUpdatedSubstitutionsMap(singleLineUserQuery, autocompleteSubstitutions); - if (!deepEqual(autocompleteSubstitutions, updatedSubstitutionsMap) && !isEmpty(updatedSubstitutionsMap)) { - setAutocompleteSubstitutions(updatedSubstitutionsMap); - } + const actionId = `page_search_query_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + const startTime = Date.now(); + + Log.info('[CMD_K_DEBUG] Page search query change started', false, { + actionId, + inputLength: userQuery.length, + previousInputLength: textInputValue.length, + timestamp: startTime, + }); + + try { + const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true); + const updatedUserQuery = getAutocompleteQueryWithComma(textInputValue, singleLineUserQuery); + setTextInputValue(updatedUserQuery); + setAutocompleteQueryValue(updatedUserQuery); + + const updatedSubstitutionsMap = getUpdatedSubstitutionsMap(singleLineUserQuery, autocompleteSubstitutions); + if (!deepEqual(autocompleteSubstitutions, updatedSubstitutionsMap) && !isEmpty(updatedSubstitutionsMap)) { + setAutocompleteSubstitutions(updatedSubstitutionsMap); + } + + if (updatedUserQuery) { + listRef.current?.updateAndScrollToFocusedIndex(0); + } else { + listRef.current?.updateAndScrollToFocusedIndex(-1); + } - if (updatedUserQuery) { - listRef.current?.updateAndScrollToFocusedIndex(0); - } else { - listRef.current?.updateAndScrollToFocusedIndex(-1); + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Page search query change completed', false, { + actionId, + duration: endTime - startTime, + finalInputLength: updatedUserQuery.length, + substitutionsUpdated: !deepEqual(autocompleteSubstitutions, updatedSubstitutionsMap) && !isEmpty(updatedSubstitutionsMap), + timestamp: endTime, + }); + } catch (error) { + const endTime = Date.now(); + Log.alert('[CMD_K_FREEZE] Page search query change failed', { + actionId, + error: String(error), + duration: endTime - startTime, + inputLength: userQuery.length, + timestamp: endTime, + }); + throw error; } }, [autocompleteSubstitutions, setTextInputValue, textInputValue], @@ -191,29 +223,93 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo const onListItemPress = useCallback( (item: OptionData | SearchQueryItem) => { - if (isSearchQueryItem(item)) { - if (!item.searchQuery) { - return; - } - - if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { - const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); - const newSearchQuery = `${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)}\u00A0`; - onSearchQueryChange(newSearchQuery); - setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); - - if (item.mapKey && item.autocompleteID) { - const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; + const actionId = `page_list_item_press_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + const startTime = Date.now(); + + Log.info('[CMD_K_DEBUG] Page list item press started', false, { + actionId, + itemType: isSearchQueryItem(item) ? 'SearchQueryItem' : 'OptionData', + searchItemType: isSearchQueryItem(item) ? item.searchItemType : undefined, + hasSearchQuery: isSearchQueryItem(item) ? !!item.searchQuery : undefined, + hasReportID: 'reportID' in item ? !!item.reportID : undefined, + hasLogin: 'login' in item ? !!item.login : undefined, + timestamp: startTime, + }); + + try { + if (isSearchQueryItem(item)) { + if (!item.searchQuery) { + Log.info('[CMD_K_DEBUG] Page list item press skipped - no search query', false, { + actionId, + itemType: 'SearchQueryItem', + timestamp: Date.now(), + }); + return; + } - setAutocompleteSubstitutions(substitutions); + if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { + const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); + const newSearchQuery = `${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)}\u00A0`; + onSearchQueryChange(newSearchQuery); + setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); + + if (item.mapKey && item.autocompleteID) { + const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; + setAutocompleteSubstitutions(substitutions); + } + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Page autocomplete suggestion handled', false, { + actionId, + duration: endTime - startTime, + trimmedQueryLength: trimmedUserSearchQuery.length, + newQueryLength: newSearchQuery.length, + hasMapKey: !!(item.mapKey && item.autocompleteID), + timestamp: endTime, + }); + } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH) { + submitSearch(item.searchQuery); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Page search submitted', false, { + actionId, + duration: endTime - startTime, + searchQuery: item.searchQuery, + timestamp: endTime, + }); } - } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH) { - submitSearch(item.searchQuery); + } else if (item?.reportID) { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Page report navigation handled', false, { + actionId, + duration: endTime - startTime, + reportID: item.reportID, + timestamp: endTime, + }); + } else if ('login' in item) { + navigateToAndOpenReport(item.login ? [item.login] : [], false); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Page user navigation handled', false, { + actionId, + duration: endTime - startTime, + hasLogin: !!item.login, + timestamp: endTime, + }); } - } else if (item?.reportID) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); - } else if ('login' in item) { - navigateToAndOpenReport(item.login ? [item.login] : [], false); + } catch (error) { + const endTime = Date.now(); + Log.alert('[CMD_K_FREEZE] Page list item press failed', { + actionId, + error: String(error), + duration: endTime - startTime, + itemType: isSearchQueryItem(item) ? 'SearchQueryItem' : 'OptionData', + searchItemType: isSearchQueryItem(item) ? item.searchItemType : undefined, + timestamp: endTime, + }); + throw error; } }, [autocompleteSubstitutions, onSearchQueryChange, submitSearch, textInputValue], diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 543c7e3f8045f..e983bea5db3e7 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -21,6 +21,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {scrollToRight} from '@libs/InputUtils'; +import Log from '@libs/Log'; import backHistory from '@libs/Navigation/helpers/backHistory'; import type {SearchOption} from '@libs/OptionsListUtils'; import type {OptionData} from '@libs/ReportUtils'; @@ -189,23 +190,55 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const onSearchQueryChange = useCallback( (userQuery: string, autoScrollToRight = false) => { - if (autoScrollToRight) { - shouldScrollRef.current = true; - } - const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true); - const updatedUserQuery = getAutocompleteQueryWithComma(textInputValue, singleLineUserQuery); - setTextInputValue(updatedUserQuery); - setAutocompleteQueryValue(updatedUserQuery); - - const updatedSubstitutionsMap = getUpdatedSubstitutionsMap(singleLineUserQuery, autocompleteSubstitutions); - if (!deepEqual(autocompleteSubstitutions, updatedSubstitutionsMap)) { - setAutocompleteSubstitutions(updatedSubstitutionsMap); - } + const actionId = `search_query_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + const startTime = Date.now(); + + Log.info('[CMD_K_DEBUG] Search query change started', false, { + actionId, + inputLength: userQuery.length, + previousInputLength: textInputValue.length, + autoScrollToRight, + timestamp: startTime, + }); + + try { + if (autoScrollToRight) { + shouldScrollRef.current = true; + } + const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true); + const updatedUserQuery = getAutocompleteQueryWithComma(textInputValue, singleLineUserQuery); + setTextInputValue(updatedUserQuery); + setAutocompleteQueryValue(updatedUserQuery); + + const updatedSubstitutionsMap = getUpdatedSubstitutionsMap(singleLineUserQuery, autocompleteSubstitutions); + if (!deepEqual(autocompleteSubstitutions, updatedSubstitutionsMap)) { + setAutocompleteSubstitutions(updatedSubstitutionsMap); + } - if (updatedUserQuery || textInputValue.length > 0) { - listRef.current?.updateAndScrollToFocusedIndex(0); - } else { - listRef.current?.updateAndScrollToFocusedIndex(-1); + if (updatedUserQuery || textInputValue.length > 0) { + listRef.current?.updateAndScrollToFocusedIndex(0); + } else { + listRef.current?.updateAndScrollToFocusedIndex(-1); + } + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Search query change completed', false, { + actionId, + duration: endTime - startTime, + finalInputLength: updatedUserQuery.length, + substitutionsUpdated: !deepEqual(autocompleteSubstitutions, updatedSubstitutionsMap), + timestamp: endTime, + }); + } catch (error) { + const endTime = Date.now(); + Log.alert('[CMD_K_FREEZE] Search query change failed', { + actionId, + error: String(error), + duration: endTime - startTime, + inputLength: userQuery.length, + timestamp: endTime, + }); + throw error; } }, [autocompleteSubstitutions, setTextInputValue, textInputValue], @@ -241,59 +274,135 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const onListItemPress = useCallback( (item: OptionData | SearchQueryItem) => { - const setFocusAndScrollToRight = () => { - InteractionManager.runAfterInteractions(() => { - if (!textInputRef.current) { - return; - } - textInputRef.current.focus(); - scrollToRight(textInputRef.current); - }); - }; + const actionId = `list_item_press_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + const startTime = Date.now(); + + Log.info('[CMD_K_DEBUG] List item press started', false, { + actionId, + itemType: isSearchQueryItem(item) ? 'SearchQueryItem' : 'OptionData', + searchItemType: isSearchQueryItem(item) ? item.searchItemType : undefined, + hasSearchQuery: isSearchQueryItem(item) ? !!item.searchQuery : undefined, + hasReportID: 'reportID' in item ? !!item.reportID : undefined, + hasLogin: 'login' in item ? !!item.login : undefined, + timestamp: startTime, + }); - if (isSearchQueryItem(item)) { - if (!item.searchQuery) { - return; + const setFocusAndScrollToRight = () => { + try { + InteractionManager.runAfterInteractions(() => { + if (!textInputRef.current) { + Log.info('[CMD_K_DEBUG] Focus skipped - no text input ref', false, { + actionId, + timestamp: Date.now(), + }); + return; + } + textInputRef.current.focus(); + scrollToRight(textInputRef.current); + }); + } catch (error) { + Log.alert('[CMD_K_FREEZE] Focus and scroll failed', { + actionId, + error: String(error), + timestamp: Date.now(), + }); } + }; - if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.CONTEXTUAL_SUGGESTION) { - const searchQuery = getContextualSearchQuery(item); - const newSearchQuery = `${searchQuery}\u00A0`; - onSearchQueryChange(newSearchQuery, true); - setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); - - const autocompleteKey = getContextualSearchAutocompleteKey(item); - if (autocompleteKey && item.autocompleteID) { - const substitutions = {...autocompleteSubstitutions, [autocompleteKey]: item.autocompleteID}; - - setAutocompleteSubstitutions(substitutions); + try { + if (isSearchQueryItem(item)) { + if (!item.searchQuery) { + Log.info('[CMD_K_DEBUG] List item press skipped - no search query', false, { + actionId, + itemType: 'SearchQueryItem', + timestamp: Date.now(), + }); + return; } - setFocusAndScrollToRight(); - } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { - const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); - const newSearchQuery = `${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)}\u00A0`; - onSearchQueryChange(newSearchQuery, true); - setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); - - if (item.mapKey && item.autocompleteID) { - const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; - setAutocompleteSubstitutions(substitutions); + if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.CONTEXTUAL_SUGGESTION) { + const searchQuery = getContextualSearchQuery(item); + const newSearchQuery = `${searchQuery}\u00A0`; + onSearchQueryChange(newSearchQuery, true); + setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); + + const autocompleteKey = getContextualSearchAutocompleteKey(item); + if (autocompleteKey && item.autocompleteID) { + const substitutions = {...autocompleteSubstitutions, [autocompleteKey]: item.autocompleteID}; + setAutocompleteSubstitutions(substitutions); + } + setFocusAndScrollToRight(); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Contextual suggestion handled', false, { + actionId, + duration: endTime - startTime, + newQueryLength: newSearchQuery.length, + hasSubstitutions: !!(autocompleteKey && item.autocompleteID), + timestamp: endTime, + }); + } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { + const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); + const newSearchQuery = `${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)}\u00A0`; + onSearchQueryChange(newSearchQuery, true); + setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); + + if (item.mapKey && item.autocompleteID) { + const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; + setAutocompleteSubstitutions(substitutions); + } + setFocusAndScrollToRight(); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Autocomplete suggestion handled', false, { + actionId, + duration: endTime - startTime, + trimmedQueryLength: trimmedUserSearchQuery.length, + newQueryLength: newSearchQuery.length, + hasMapKey: !!(item.mapKey && item.autocompleteID), + timestamp: endTime, + }); + } else { + submitSearch(item.searchQuery); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Search submitted', false, { + actionId, + duration: endTime - startTime, + searchQuery: item.searchQuery, + timestamp: endTime, + }); } - // needed for android mWeb - setFocusAndScrollToRight(); } else { - submitSearch(item.searchQuery); + backHistory(() => { + onRouterClose(); + if (item?.reportID) { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item.reportID)); + } else if ('login' in item) { + navigateToAndOpenReport(item.login ? [item.login] : [], false); + } + }); + + const endTime = Date.now(); + Log.info('[CMD_K_DEBUG] Navigation item handled', false, { + actionId, + duration: endTime - startTime, + reportID: item?.reportID, + hasLogin: 'login' in item ? !!item.login : false, + timestamp: endTime, + }); } - } else { - backHistory(() => { - onRouterClose(); - if (item?.reportID) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item.reportID)); - } else if ('login' in item) { - navigateToAndOpenReport(item.login ? [item.login] : [], false); - } + } catch (error) { + const endTime = Date.now(); + Log.alert('[CMD_K_FREEZE] List item press failed', { + actionId, + error: String(error), + duration: endTime - startTime, + itemType: isSearchQueryItem(item) ? 'SearchQueryItem' : 'OptionData', + searchItemType: isSearchQueryItem(item) ? item.searchItemType : undefined, + timestamp: endTime, }); + throw error; } }, [autocompleteSubstitutions, onRouterClose, onSearchQueryChange, submitSearch, textInputValue], diff --git a/src/hooks/useFastSearchFromOptions.ts b/src/hooks/useFastSearchFromOptions.ts index 670ac8f14e4c4..8b04623c36c55 100644 --- a/src/hooks/useFastSearchFromOptions.ts +++ b/src/hooks/useFastSearchFromOptions.ts @@ -3,6 +3,7 @@ import {useCallback, useEffect, useRef, useState} from 'react'; import {InteractionManager} from 'react-native'; import Timing from '@libs/actions/Timing'; import FastSearch from '@libs/FastSearch'; +import Log from '@libs/Log'; import type {Options as OptionsListType, ReportAndPersonalDetailOptions} from '@libs/OptionsListUtils'; import {filterUserToInvite, isSearchStringMatch} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; @@ -86,31 +87,97 @@ function useFastSearchFromOptions( if (prevOptions && shallowCompareOptions(prevOptions, options)) { return; } + + const actionId = `fast_search_tree_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + InteractionManager.runAfterInteractions(() => { - prevOptionsRef.current = options; - prevFastSearchRef.current?.dispose(); - newFastSearch = FastSearch.createFastSearch( - [ - { - data: options.personalDetails, - toSearchableString: personalDetailToSearchString, - uniqueId: getPersonalDetailUniqueId, - }, - { - data: options.recentReports, - toSearchableString: recentReportToSearchString, - uniqueId: getRecentReportUniqueId, - }, - ], + const startTime = Date.now(); + + Performance.markStart(CONST.TIMING.FAST_SEARCH_TREE_CREATION); + Log.info('[CMD_K_DEBUG] FastSearch tree creation started', false, { + actionId, + personalDetailsCount: options.personalDetails.length, + recentReportsCount: options.recentReports.length, + hasExistingTree: !!prevFastSearchRef.current, + timestamp: startTime, + }); + + try { + prevOptionsRef.current = options; + + // Dispose existing tree if present + if (prevFastSearchRef.current) { + const disposeStartTime = Date.now(); + prevFastSearchRef.current.dispose(); + Log.info('[CMD_K_DEBUG] FastSearch tree disposed', false, { + actionId, + disposeTime: Date.now() - disposeStartTime, + timestamp: Date.now(), + }); + } - {shouldStoreSearchableStrings: true}, - ); - setFastSearch(newFastSearch); - prevFastSearchRef.current = newFastSearch; - setIsInitialized(true); + newFastSearch = FastSearch.createFastSearch( + [ + { + data: options.personalDetails, + toSearchableString: personalDetailToSearchString, + uniqueId: getPersonalDetailUniqueId, + }, + { + data: options.recentReports, + toSearchableString: recentReportToSearchString, + uniqueId: getRecentReportUniqueId, + }, + ], + + {shouldStoreSearchableStrings: true}, + ); + + setFastSearch(newFastSearch); + prevFastSearchRef.current = newFastSearch; + setIsInitialized(true); + + const endTime = Date.now(); + Performance.markEnd(CONST.TIMING.FAST_SEARCH_TREE_CREATION); + Log.info('[CMD_K_DEBUG] FastSearch tree creation completed', false, { + actionId, + duration: endTime - startTime, + totalItems: options.personalDetails.length + options.recentReports.length, + isInitialized: true, + timestamp: endTime, + }); + } catch (error) { + const endTime = Date.now(); + Performance.markEnd(CONST.TIMING.FAST_SEARCH_TREE_CREATION); + Log.alert('[CMD_K_FREEZE] FastSearch tree creation failed', { + actionId, + error: String(error), + duration: endTime - startTime, + personalDetailsCount: options.personalDetails.length, + recentReportsCount: options.recentReports.length, + timestamp: endTime, + }); + throw error; + } }); - return () => newFastSearch?.dispose(); + return () => { + try { + if (newFastSearch) { + Log.info('[CMD_K_DEBUG] FastSearch tree cleanup', false, { + actionId, + timestamp: Date.now(), + }); + newFastSearch.dispose(); + } + } catch (error) { + Log.alert('[CMD_K_FREEZE] FastSearch tree cleanup failed', { + actionId, + error: String(error), + timestamp: Date.now(), + }); + } + }; }, [options]); useEffect(() => () => prevFastSearchRef.current?.dispose(), []);