From b368e72b67ee9e589e9fa15309c0b0f4c55266d7 Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Tue, 23 Jun 2026 16:55:43 -0300 Subject: [PATCH 01/11] Add RAM_ONLY_SEARCH_RESULT_REPORT_IDS Onyx key and type --- src/ONYXKEYS.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index cbeff2cf3b78..fd6a378a246c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -41,6 +41,9 @@ const ONYXKEYS = { /** Boolean flag set whenever we are searching for reports in the server */ RAM_ONLY_IS_SEARCHING_FOR_REPORTS: 'isSearchingForReports', + /** Ordered reportIDs from the latest SearchForReports response, used to display server search results in the tier order Auth returned. RAM-only since it is only relevant for the current search session. */ + RAM_ONLY_SEARCH_RESULT_REPORT_IDS: 'searchResultReportIDs', + /** Boolean flag indicating a SignInWithShortLivedAuthToken request is in flight. RAM-only so an interrupted request never persists a stuck `true` to IndexedDB and blocks future reauth attempts. */ RAM_ONLY_IS_AUTHENTICATING_WITH_SHORT_LIVED_TOKEN: 'isAuthenticatingWithShortLivedToken', @@ -1568,6 +1571,7 @@ type OnyxValuesMapping = { [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; [ONYXKEYS.ONBOARDING_LAST_VISITED_PATH]: string; [ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS]: boolean; + [ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS]: string[]; [ONYXKEYS.RAM_ONLY_IS_AUTHENTICATING_WITH_SHORT_LIVED_TOKEN]: boolean; [ONYXKEYS.LAST_VISITED_PATH]: string | undefined; [ONYXKEYS.REPORT_LAST_VISIT_TIMES]: OnyxTypes.ReportLastVisitTimes; From c14d09232eb5080d7bb9eccac82eb9d0dc077210 Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Tue, 23 Jun 2026 16:55:43 -0300 Subject: [PATCH 02/11] Register search result order key as RAM-only --- src/setup/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/setup/index.ts b/src/setup/index.ts index ce1aa4999eb7..348e26807b34 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -65,6 +65,7 @@ export default function () { ONYXKEYS.RAM_ONLY_UPDATE_AVAILABLE, ONYXKEYS.RAM_ONLY_UPDATE_REQUIRED, ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS, + ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS, ONYXKEYS.RAM_ONLY_IS_AUTHENTICATING_WITH_SHORT_LIVED_TOKEN, ONYXKEYS.RAM_ONLY_WALLET_ONFIDO, ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE, From 1bb4fb518695afff59737fb4f307b59fcf0788df Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Tue, 23 Jun 2026 16:55:43 -0300 Subject: [PATCH 03/11] Order search results by Auth's tier order --- .../Search/SearchAutocompleteList.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 270127671392..f6b5ae2a926e 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -172,6 +172,7 @@ function SearchAutocompleteList({ const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER); const allCards = personalAndWorkspaceCards ?? CONST.EMPTY_OBJECT; const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [searchResultReportIDs] = useOnyx(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserEmail = currentUserPersonalDetails.email ?? ''; const currentUserAccountID = currentUserPersonalDetails.accountID; @@ -385,8 +386,16 @@ function SearchAutocompleteList({ reportOptions.push(searchOptions.userToInvite); } + // When the server has returned a tier-ranked order for this search, display results in that order + // instead of the client-side kind/recency order. Reports absent from the list sort to the end. + if (searchResultReportIDs && searchResultReportIDs.length > 0) { + const rankByReportID = new Map(searchResultReportIDs.map((reportID, index) => [reportID, index])); + const rankOf = (option: OptionData) => (option.reportID === undefined ? Number.MAX_SAFE_INTEGER : (rankByReportID.get(option.reportID) ?? Number.MAX_SAFE_INTEGER)); + reportOptions.sort((a, b) => rankOf(a) - rankOf(b)); + } + return reportOptions.slice(0, 20); - }, [autocompleteQueryValue, searchOptions]); + }, [autocompleteQueryValue, searchOptions, searchResultReportIDs]); // Locked rank map (keyForList -> originalIndex) capturing the order of locally-known // results at the moment the query changes. Recomputed only when the query changes, so server @@ -504,8 +513,17 @@ function SearchAutocompleteList({ customHeader: skeletonHeader, }); } + } else if (searchResultReportIDs && searchResultReportIDs.length > 0) { + // The server returned a tier-ranked order for this query (already applied to recentReportsOptions), + // so render a single list in that order rather than splitting into local/server sections — splitting + // would group local matches separately and break the global tier ordering. + if (nextStyledRecentReports.length > 0 || !isLoadingOptions) { + pushSection({title: translate('search.serverResults'), data: nextStyledRecentReports, sectionIndex: sectionIndex++}); + } else { + pushSection({title: undefined, data: [], sectionIndex: sectionIndex++, customHeader: skeletonHeader}); + } } else { - // Active search: split rows into local (frozen order) and server sections. + // Active search without a server order yet: split rows into local (frozen order) and server sections. const localRows: AutocompleteListItem[] = []; const serverRows: AutocompleteListItem[] = []; for (const item of nextStyledRecentReports) { @@ -568,6 +586,7 @@ function SearchAutocompleteList({ recentSearchesData, searchOptions, searchQueryItems, + searchResultReportIDs, styles, translate, isLoadingOptions, From 9c2c1d40394d81b9c26dff413b8ea7f63c817e6e Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Tue, 23 Jun 2026 16:55:44 -0300 Subject: [PATCH 04/11] Test search results follow Auth's tier order --- tests/unit/SearchAutocompleteListTest.tsx | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/unit/SearchAutocompleteListTest.tsx b/tests/unit/SearchAutocompleteListTest.tsx index 5f4cfc2e4973..b58d41a86f51 100644 --- a/tests/unit/SearchAutocompleteListTest.tsx +++ b/tests/unit/SearchAutocompleteListTest.tsx @@ -422,6 +422,65 @@ describe('SearchAutocompleteList', () => { expect(relevantOrder.indexOf('Charlie Report')).toBeLessThan(relevantOrder.indexOf('NewServer Report')); }); + it('should order all search results by the order Auth returned', async () => { + await waitForBatchedUpdates(); + await Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + }); + + render(); + await flushAllUpdates(); + + // Type a search query to freeze the local rank for the locally-known reports. + const textInput = screen.getByTestId('search-autocomplete-text-input'); + fireEvent.changeText(textInput, 'test'); + await flushAllUpdates(); + + // Simulate server results arriving: three server-only reports the client did not know about, + // returned by getSearchOptions in ascending reportID order (201, 202, 203). + getSearchOptionsSpy.mockReturnValue({ + options: { + recentReports: [ + {reportID: '201', keyForList: '201', text: 'ServerOne Report', alternateText: 'one alt', lastMessageText: 'one'}, + {reportID: '202', keyForList: '202', text: 'ServerTwo Report', alternateText: 'two alt', lastMessageText: 'two'}, + {reportID: '203', keyForList: '203', text: 'ServerThree Report', alternateText: 'three alt', lastMessageText: 'three'}, + ], + personalDetails: [], + currentUserOption: null, + userToInvite: null, + }, + }); + mockUseFilteredOptions.mockReturnValue({ + options: {...mockedOptions}, + isLoading: false, + loadMore: jest.fn(), + hasMore: false, + isLoadingMore: false, + }); + + // Auth returned a DIFFERENT (tier) order: 203, 201, 202. + await act(async () => { + await Onyx.set(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS, ['203', '201', '202']); + await Onyx.set(ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS, false); + }); + await flushAllUpdates(); + + await waitFor(() => { + expect(screen.getByText('Search results')).toBeTruthy(); + }); + + const names = screen + .queryAllByText(/Report$/) + .map((el) => (typeof el.props.children === 'string' ? (el.props.children as string) : '')) + .filter((name) => ['ServerOne Report', 'ServerTwo Report', 'ServerThree Report'].includes(name)); + + // The server section must follow Auth's order (203, 201, 202), not the order getSearchOptions returned. + expect(names.indexOf('ServerThree Report')).toBeLessThan(names.indexOf('ServerOne Report')); + expect(names.indexOf('ServerOne Report')).toBeLessThan(names.indexOf('ServerTwo Report')); + }); + // Regression test for https://github.com/Expensify/App/issues/93009: after the two-section switcher was // introduced, the first matched chat was no longer highlighted because the highlight focused a fixed flat // index that now lands on the "Recent chats" section header row instead of the first result. As a result From 251d4fb8e9a35bab5a56b7c0019c3b771bbe463f Mon Sep 17 00:00:00 2001 From: "Carlos Miceli (via MelvinBot)" Date: Wed, 24 Jun 2026 03:01:34 +0000 Subject: [PATCH 05/11] Fix: remove unnecessary type assertion flagged by ESLint Co-authored-by: Carlos Miceli --- tests/unit/SearchAutocompleteListTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/SearchAutocompleteListTest.tsx b/tests/unit/SearchAutocompleteListTest.tsx index b58d41a86f51..62dfc1c2f06c 100644 --- a/tests/unit/SearchAutocompleteListTest.tsx +++ b/tests/unit/SearchAutocompleteListTest.tsx @@ -473,7 +473,7 @@ describe('SearchAutocompleteList', () => { const names = screen .queryAllByText(/Report$/) - .map((el) => (typeof el.props.children === 'string' ? (el.props.children as string) : '')) + .map((el) => (typeof el.props.children === 'string' ? el.props.children : '')) .filter((name) => ['ServerOne Report', 'ServerTwo Report', 'ServerThree Report'].includes(name)); // The server section must follow Auth's order (203, 201, 202), not the order getSearchOptions returned. From 6883d69c3c8600dcc2a5560bcb17d922274a8be5 Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Wed, 24 Jun 2026 16:51:51 -0300 Subject: [PATCH 06/11] Rank the selfDM first in search regardless of the server order --- src/components/Search/SearchAutocompleteList.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index f6b5ae2a926e..a7370d06ca95 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -390,7 +390,14 @@ function SearchAutocompleteList({ // instead of the client-side kind/recency order. Reports absent from the list sort to the end. if (searchResultReportIDs && searchResultReportIDs.length > 0) { const rankByReportID = new Map(searchResultReportIDs.map((reportID, index) => [reportID, index])); - const rankOf = (option: OptionData) => (option.reportID === undefined ? Number.MAX_SAFE_INTEGER : (rankByReportID.get(option.reportID) ?? Number.MAX_SAFE_INTEGER)); + const rankOf = (option: OptionData) => { + // The selfDM always sorts first when it matches — it's always in Onyx, so it ranks ahead of the server order + // (and need not be included in it). + if (option.isSelfDM) { + return -1; + } + return option.reportID === undefined ? Number.MAX_SAFE_INTEGER : (rankByReportID.get(option.reportID) ?? Number.MAX_SAFE_INTEGER); + }; reportOptions.sort((a, b) => rankOf(a) - rankOf(b)); } From aa646ccb8624315bb570e3add7bad9d6e4417e92 Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Wed, 24 Jun 2026 18:39:17 -0300 Subject: [PATCH 07/11] Test selfDM ranks first when absent from the server order --- tests/unit/SearchAutocompleteListTest.tsx | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/unit/SearchAutocompleteListTest.tsx b/tests/unit/SearchAutocompleteListTest.tsx index 62dfc1c2f06c..9c2398ebc5e1 100644 --- a/tests/unit/SearchAutocompleteListTest.tsx +++ b/tests/unit/SearchAutocompleteListTest.tsx @@ -481,6 +481,63 @@ describe('SearchAutocompleteList', () => { expect(names.indexOf('ServerOne Report')).toBeLessThan(names.indexOf('ServerTwo Report')); }); + it('should rank the selfDM first even when it is absent from the server order', async () => { + await waitForBatchedUpdates(); + await Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + }); + + render(); + await flushAllUpdates(); + + const textInput = screen.getByTestId('search-autocomplete-text-input'); + fireEvent.changeText(textInput, 'test'); + await flushAllUpdates(); + + // The selfDM plus two server reports; the server order (below) does NOT include the selfDM. + getSearchOptionsSpy.mockReturnValue({ + options: { + recentReports: [ + {reportID: '301', keyForList: '301', text: 'ServerA Report', alternateText: 'a', lastMessageText: 'a'}, + {reportID: '999', keyForList: '999', text: 'MySelf Report', alternateText: 'me', lastMessageText: 'me', isSelfDM: true}, + {reportID: '302', keyForList: '302', text: 'ServerB Report', alternateText: 'b', lastMessageText: 'b'}, + ], + personalDetails: [], + currentUserOption: null, + userToInvite: null, + }, + }); + mockUseFilteredOptions.mockReturnValue({ + options: {...mockedOptions}, + isLoading: false, + loadMore: jest.fn(), + hasMore: false, + isLoadingMore: false, + }); + + // Server order lists only the two non-selfDM reports. + await act(async () => { + await Onyx.set(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS, ['301', '302']); + await Onyx.set(ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS, false); + }); + await flushAllUpdates(); + + await waitFor(() => { + expect(screen.getByText('Search results')).toBeTruthy(); + }); + + const names = screen + .queryAllByText(/Report$/) + .map((el) => (typeof el.props.children === 'string' ? el.props.children : '')) + .filter((name) => ['MySelf Report', 'ServerA Report', 'ServerB Report'].includes(name)); + + // The selfDM leads, even though it isn't in the server order. + expect(names.indexOf('MySelf Report')).toBe(0); + expect(names.indexOf('MySelf Report')).toBeLessThan(names.indexOf('ServerA Report')); + }); + // Regression test for https://github.com/Expensify/App/issues/93009: after the two-section switcher was // introduced, the first matched chat was no longer highlighted because the highlight focused a fixed flat // index that now lands on the "Recent chats" section header row instead of the first result. As a result From 17d71850a52352e2010bfc7fde9849ecdc44da9e Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Thu, 25 Jun 2026 00:24:15 -0300 Subject: [PATCH 08/11] Trim the RAM-only justification from the searchResultReportIDs comment --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index fd6a378a246c..8161315b134a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -41,7 +41,7 @@ const ONYXKEYS = { /** Boolean flag set whenever we are searching for reports in the server */ RAM_ONLY_IS_SEARCHING_FOR_REPORTS: 'isSearchingForReports', - /** Ordered reportIDs from the latest SearchForReports response, used to display server search results in the tier order Auth returned. RAM-only since it is only relevant for the current search session. */ + /** Ordered reportIDs from the latest SearchForReports response, used to display server search results in the tier order Auth returned. */ RAM_ONLY_SEARCH_RESULT_REPORT_IDS: 'searchResultReportIDs', /** Boolean flag indicating a SignInWithShortLivedAuthToken request is in flight. RAM-only so an interrupted request never persists a stuck `true` to IndexedDB and blocks future reauth attempts. */ From 2e2f2669c583077a27ca4957c44c2f464a61eb2a Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Thu, 25 Jun 2026 00:45:33 -0300 Subject: [PATCH 09/11] Clear the stale search result order when a new search starts --- src/libs/actions/Report/index.ts | 4 ++++ tests/actions/ReportTest.ts | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 593da3386411..2dcc2441b686 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -5585,6 +5585,10 @@ function searchForReports(isOffline: boolean, searchInput: string, policyID?: st function performServerSearch(searchInput: string, policyID?: string, isUserSearch = false) { // We are not getting isOffline from components as useEffect change will re-trigger the search on network change const isOffline = isOfflineNetwork(); + + // Clear the previous search's server-provided result order so it isn't applied to this query before a fresh response arrives. + Onyx.set(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS, []); + if (isOffline || !searchInput.trim().length) { Onyx.set(ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS, false); return; diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 16e7cfd9c40b..c6dbb0ec85a1 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -4642,6 +4642,17 @@ describe('actions/Report', () => { const lowerCaseRequest = PersistedRequests.getAll().at(1); expect(upperCaseRequest?.data?.searchInput).toBe(lowerCaseRequest?.data?.searchInput); }); + + it("clears the previous search's result order so a new query does not reuse it", async () => { + await Onyx.set(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS, ['1', '2', '3']); + await waitForBatchedUpdates(); + + Report.searchInServer('new query'); + await waitForBatchedUpdates(); + + const orderedReportIDs = await getOnyxValue(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS); + expect(orderedReportIDs).toEqual([]); + }); }); describe('searchUserInServer', () => { From bfc4167bbc6504ca20b9c7eff91132667c591e0e Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Thu, 25 Jun 2026 09:49:51 -0300 Subject: [PATCH 10/11] Widen the candidate pool to the full match set when an Auth order is present --- src/components/Search/SearchAutocompleteList.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index a7370d06ca95..8bd51df51185 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -213,7 +213,8 @@ function SearchAutocompleteList({ isUsedInChatFinder: true, includeReadOnly: true, searchQuery: autocompleteQueryValue, - maxResults: CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, + // With an Auth order, keep every matching report instead of just the 20 most recent, so the Auth-order sort below can't drop a top-ranked but old one. + maxResults: searchResultReportIDs && searchResultReportIDs.length > 0 ? listOptions.reports.length : CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, includeUserToInvite: true, includeRecentReports: true, includeCurrentUser: true, @@ -245,6 +246,7 @@ function SearchAutocompleteList({ sortedActions, conciergeReportID, isTrackIntentUser, + searchResultReportIDs, ]); const [isInitialRender, setIsInitialRender] = useState(true); From e64349d3f7fcf0e20d4b211c6c832e446965fed5 Mon Sep 17 00:00:00 2001 From: Carlos Miceli Date: Thu, 25 Jun 2026 09:49:52 -0300 Subject: [PATCH 11/11] Test that an Auth order widens the search candidate pool --- tests/unit/SearchAutocompleteListTest.tsx | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unit/SearchAutocompleteListTest.tsx b/tests/unit/SearchAutocompleteListTest.tsx index 9c2398ebc5e1..25f6da0b43a5 100644 --- a/tests/unit/SearchAutocompleteListTest.tsx +++ b/tests/unit/SearchAutocompleteListTest.tsx @@ -538,6 +538,34 @@ describe('SearchAutocompleteList', () => { expect(names.indexOf('MySelf Report')).toBeLessThan(names.indexOf('ServerA Report')); }); + it('widens the candidate pool to the full pre-filtered set once the server returns an order', async () => { + await waitForBatchedUpdates(); + await Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + }); + + render(); + await flushAllUpdates(); + + const textInput = screen.getByTestId('search-autocomplete-text-input'); + fireEvent.changeText(textInput, 'test'); + await flushAllUpdates(); + + // Before a server order arrives, results are capped to the default suggestion limit (by recency). + expect(getSearchOptionsSpy).toHaveBeenLastCalledWith(expect.objectContaining({maxResults: CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS})); + + await act(async () => { + await Onyx.set(ONYXKEYS.RAM_ONLY_SEARCH_RESULT_REPORT_IDS, ['101']); + await Onyx.set(ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS, false); + }); + await flushAllUpdates(); + + // With a server order present, the cap is lifted to the full pre-filtered pool so a low-recency top-tier report isn't culled before the Auth-order sort. + expect(getSearchOptionsSpy).toHaveBeenLastCalledWith(expect.objectContaining({maxResults: mockedOptions.reports.length})); + }); + // Regression test for https://github.com/Expensify/App/issues/93009: after the two-section switcher was // introduced, the first matched chat was no longer highlighted because the highlight focused a fixed flat // index that now lands on the "Recent chats" section header row instead of the first result. As a result