From c577abfeeef0c37532fa3ab57a8e5e4c6858defd Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 3 Nov 2025 17:51:27 +0530
Subject: [PATCH 01/16] Add loading UI for search page menu + default to
 approve section for admins

---
 src/components/Search/index.tsx               |   4 +
 src/hooks/useSearchTypeMenu.tsx               |  16 +-
 src/hooks/useSearchTypeMenuSections.ts        |  16 +-
 .../useSuggestedSearchDefaultNavigation.ts    |  43 ++++
 src/pages/Search/SearchTypeMenu.tsx           | 122 ++++++-----
 src/pages/Search/SuggestedSearchSkeleton.tsx  | 103 +++++++++
 ...useSuggestedSearchDefaultNavigationTest.ts | 196 ++++++++++++++++++
 tests/unit/useSearchTypeMenuSectionsTest.ts   | 123 +++++++++++
 8 files changed, 566 insertions(+), 57 deletions(-)
 create mode 100644 src/hooks/useSuggestedSearchDefaultNavigation.ts
 create mode 100644 src/pages/Search/SuggestedSearchSkeleton.tsx
 create mode 100644 tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts
 create mode 100644 tests/unit/useSearchTypeMenuSectionsTest.ts

diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 26a8eda50c60..3298ad78fb77 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -424,6 +424,10 @@ function Search({
             return;
         }
 
+        if (prevIsOffline && !isOffline) {
+            openSearch();
+        }
+
         handleSearch({queryJSON, searchKey, offset, shouldCalculateTotals, prevReportsLength: dataLength});
 
         // We don't need to run the effect on change of isFocused.
diff --git a/src/hooks/useSearchTypeMenu.tsx b/src/hooks/useSearchTypeMenu.tsx
index c5484f9a2288..f6628c9badd0 100644
--- a/src/hooks/useSearchTypeMenu.tsx
+++ b/src/hooks/useSearchTypeMenu.tsx
@@ -3,6 +3,7 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react';
 import type {OnyxCollection} from 'react-native-onyx';
 import {usePersonalDetails} from '@components/OnyxListItemProvider';
 import type {PopoverMenuItem} from '@components/PopoverMenu';
+import {useSearchContext} from '@components/Search/SearchContext';
 import type {SearchQueryJSON} from '@components/Search/types';
 import ThreeDotsMenu from '@components/ThreeDotsMenu';
 import {clearAllFilters} from '@libs/actions/Search';
@@ -24,6 +25,7 @@ import useLocalize from './useLocalize';
 import useOnyx from './useOnyx';
 import useSearchTypeMenuSections from './useSearchTypeMenuSections';
 import useSingleExecution from './useSingleExecution';
+import useSuggestedSearchDefaultNavigation from './useSuggestedSearchDefaultNavigation';
 import useTheme from './useTheme';
 import useThemeStyles from './useThemeStyles';
 import useWindowDimensions from './useWindowDimensions';
@@ -36,7 +38,8 @@ export default function useSearchTypeMenu(queryJSON: SearchQueryJSON) {
     const {singleExecution} = useSingleExecution();
     const {windowHeight} = useWindowDimensions();
     const {translate} = useLocalize();
-    const {typeMenuSections} = useSearchTypeMenuSections();
+    const {typeMenuSections, shouldShowSuggestedSearchSkeleton} = useSearchTypeMenuSections();
+    const {clearSelectedTransactions} = useSearchContext();
     const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch();
     const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
     const personalDetails = usePersonalDetails();
@@ -51,6 +54,14 @@ export default function useSearchTypeMenu(queryJSON: SearchQueryJSON) {
 
     const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
     const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true});
+    const flattenedMenuItems = useMemo(() => typeMenuSections.flatMap((section) => section.menuItems), [typeMenuSections]);
+
+    useSuggestedSearchDefaultNavigation({
+        shouldShowSkeleton: shouldShowSuggestedSearchSkeleton,
+        flattenedMenuItems,
+        similarSearchHash,
+        clearSelectedTransactions,
+    });
 
     // this is a performance fix, rendering popover menu takes a lot of time and we don't need this component initially, that's why we postpone rendering it until everything else is rendered
     const [delayPopoverMenuFirstRender, setDelayPopoverMenuFirstRender] = useState(true);
@@ -131,9 +142,8 @@ export default function useSearchTypeMenu(queryJSON: SearchQueryJSON) {
             return -1;
         }
 
-        const flattenedMenuItems = typeMenuSections.map((section) => section.menuItems).flat();
         return flattenedMenuItems.findIndex((item) => item.similarSearchHash === similarSearchHash);
-    }, [similarSearchHash, isSavedSearchActive, typeMenuSections]);
+    }, [similarSearchHash, isSavedSearchActive, flattenedMenuItems]);
 
     const popoverMenuItems = useMemo(() => {
         return typeMenuSections
diff --git a/src/hooks/useSearchTypeMenuSections.ts b/src/hooks/useSearchTypeMenuSections.ts
index 847c3e99a53b..67b92c3fec6b 100644
--- a/src/hooks/useSearchTypeMenuSections.ts
+++ b/src/hooks/useSearchTypeMenuSections.ts
@@ -113,6 +113,16 @@ const useSearchTypeMenuSections = () => {
         openCreateReportConfirmation();
     }, [pendingReportCreation, openCreateReportConfirmation]);
 
+    const isSuggestedSearchDataReady = useMemo(() => {
+        const policiesList = Object.values(allPolicies ?? {}).filter((policy): policy is NonNullable<typeof policy> => policy !== null && policy !== undefined);
+
+        if (policiesList.length === 0) {
+            return true;
+        }
+
+        return policiesList.some((policy) => policy.employeeList !== undefined && policy.exporter !== undefined);
+    }, [allPolicies]);
+
     const typeMenuSections = useMemo(
         () =>
             createTypeMenuSections(
@@ -145,7 +155,11 @@ const useSearchTypeMenuSections = () => {
         ],
     );
 
-    return {typeMenuSections, CreateReportConfirmationModal};
+    return {
+        typeMenuSections,
+        CreateReportConfirmationModal,
+        shouldShowSuggestedSearchSkeleton: !isSuggestedSearchDataReady,
+    };
 };
 
 export default useSearchTypeMenuSections;
diff --git a/src/hooks/useSuggestedSearchDefaultNavigation.ts b/src/hooks/useSuggestedSearchDefaultNavigation.ts
new file mode 100644
index 000000000000..04b59d3ffff8
--- /dev/null
+++ b/src/hooks/useSuggestedSearchDefaultNavigation.ts
@@ -0,0 +1,43 @@
+import {useEffect, useRef} from 'react';
+import {clearAllFilters} from '@libs/actions/Search';
+import Navigation from '@libs/Navigation/Navigation';
+import type {SearchTypeMenuItem} from '@libs/SearchUIUtils';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+type UseSuggestedSearchDefaultNavigationParams = {
+    shouldShowSkeleton: boolean;
+    flattenedMenuItems: SearchTypeMenuItem[];
+    similarSearchHash?: number;
+    clearSelectedTransactions: () => void;
+};
+
+function useSuggestedSearchDefaultNavigation({shouldShowSkeleton, flattenedMenuItems, similarSearchHash, clearSelectedTransactions}: UseSuggestedSearchDefaultNavigationParams) {
+    const hasShownSkeleton = useRef(false);
+
+    useEffect(() => {
+        if (shouldShowSkeleton) {
+            hasShownSkeleton.current = true;
+            return;
+        }
+
+        if (!hasShownSkeleton.current) {
+            return;
+        }
+
+        hasShownSkeleton.current = false;
+
+        const defaultMenuItem =
+            flattenedMenuItems.find((item) => item.key === CONST.SEARCH.SEARCH_KEYS.APPROVE) ?? flattenedMenuItems.find((item) => item.key === CONST.SEARCH.SEARCH_KEYS.SUBMIT);
+
+        if (!defaultMenuItem || similarSearchHash === defaultMenuItem.similarSearchHash) {
+            return;
+        }
+
+        clearAllFilters();
+        clearSelectedTransactions();
+        Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: defaultMenuItem.searchQuery}));
+    }, [shouldShowSkeleton, flattenedMenuItems, similarSearchHash, clearSelectedTransactions]);
+}
+
+export default useSuggestedSearchDefaultNavigation;
diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index a8592b6e014b..722cabac0531 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -20,6 +20,7 @@ import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections';
 import useSingleExecution from '@hooks/useSingleExecution';
+import useSuggestedSearchDefaultNavigation from '@hooks/useSuggestedSearchDefaultNavigation';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {clearAllFilters} from '@libs/actions/Search';
 import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils';
@@ -35,6 +36,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import type {SaveSearchItem} from '@src/types/onyx/SaveSearch';
 import SavedSearchItemThreeDotMenu from './SavedSearchItemThreeDotMenu';
+import SuggestedSearchSkeleton from './SuggestedSearchSkeleton';
 
 type SearchTypeMenuProps = {
     queryJSON: SearchQueryJSON | undefined;
@@ -47,7 +49,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
     const {singleExecution} = useSingleExecution();
     const {translate} = useLocalize();
     const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES, {canBeMissing: true});
-    const {typeMenuSections, CreateReportConfirmationModal} = useSearchTypeMenuSections();
+    const {typeMenuSections, CreateReportConfirmationModal, shouldShowSuggestedSearchSkeleton} = useSearchTypeMenuSections();
     const isFocused = useIsFocused();
     const {
         shouldShowProductTrainingTooltip: shouldShowSavedSearchTooltip,
@@ -82,6 +84,15 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
         });
     }, [typeMenuSections]);
 
+    const flattenedMenuItems = useMemo(() => typeMenuSections.flatMap((section) => section.menuItems), [typeMenuSections]);
+
+    useSuggestedSearchDefaultNavigation({
+        shouldShowSkeleton: shouldShowSuggestedSearchSkeleton,
+        flattenedMenuItems,
+        similarSearchHash,
+        clearSelectedTransactions,
+    });
+
     const getOverflowMenu = useCallback((itemName: string, itemHash: number, itemQuery: string) => getOverflowMenuUtil(itemName, itemHash, itemQuery, showDeleteModal), [showDeleteModal]);
     const createSavedSearchMenuItem = useCallback(
         (item: SaveSearchItem, key: string, index: number) => {
@@ -205,9 +216,8 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
             return -1;
         }
 
-        const flattenedMenuItems = typeMenuSections.map((section) => section.menuItems).flat();
         return flattenedMenuItems.findIndex((item) => item.similarSearchHash === similarSearchHash);
-    }, [similarSearchHash, isSavedSearchActive, typeMenuSections]);
+    }, [similarSearchHash, isSavedSearchActive, flattenedMenuItems]);
 
     return (
         <>
@@ -217,60 +227,66 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
                 ref={scrollViewRef}
                 showsVerticalScrollIndicator={false}
             >
-                <View style={[styles.pb4, styles.mh3, styles.gap4]}>
-                    {typeMenuSections.map((section, sectionIndex) => (
-                        <View key={section.translationPath}>
-                            <Text style={styles.sectionTitle}>{translate(section.translationPath)}</Text>
+                {shouldShowSuggestedSearchSkeleton ? (
+                    <View style={[styles.pb4, styles.mh3]}>
+                        <SuggestedSearchSkeleton />
+                    </View>
+                ) : (
+                    <View style={[styles.pb4, styles.mh3, styles.gap4]}>
+                        {typeMenuSections.map((section, sectionIndex) => (
+                            <View key={section.translationPath}>
+                                <Text style={styles.sectionTitle}>{translate(section.translationPath)}</Text>
 
-                            {section.translationPath === 'search.savedSearchesMenuItemTitle' ? (
-                                <>
-                                    {renderSavedSearchesSection(savedSearchesMenuItems)}
-                                    {/* DeleteConfirmModal is a stable JSX element returned by the hook.
-                                    Returning the element directly keeps the component identity across re-renders so React
-                                    can play its exit animation instead of removing it instantly. */}
-                                    {DeleteConfirmModal}
-                                </>
-                            ) : (
-                                <>
-                                    {section.menuItems.map((item, itemIndex) => {
-                                        const previousItemCount = typeMenuSections.slice(0, sectionIndex).reduce((acc, sec) => acc + sec.menuItems.length, 0);
-                                        const flattenedIndex = previousItemCount + itemIndex;
-                                        const focused = activeItemIndex === flattenedIndex;
+                                {section.translationPath === 'search.savedSearchesMenuItemTitle' ? (
+                                    <>
+                                        {renderSavedSearchesSection(savedSearchesMenuItems)}
+                                        {/* DeleteConfirmModal is a stable JSX element returned by the hook.
+                                        Returning the element directly keeps the component identity across re-renders so React
+                                        can play its exit animation instead of removing it instantly. */}
+                                        {DeleteConfirmModal}
+                                    </>
+                                ) : (
+                                    <>
+                                        {section.menuItems.map((item, itemIndex) => {
+                                            const previousItemCount = typeMenuSections.slice(0, sectionIndex).reduce((acc, sec) => acc + sec.menuItems.length, 0);
+                                            const flattenedIndex = previousItemCount + itemIndex;
+                                            const focused = activeItemIndex === flattenedIndex;
 
-                                        const onPress = singleExecution(() => {
-                                            clearAllFilters();
-                                            clearSelectedTransactions();
-                                            Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: item.searchQuery}));
-                                        });
+                                            const onPress = singleExecution(() => {
+                                                clearAllFilters();
+                                                clearSelectedTransactions();
+                                                Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: item.searchQuery}));
+                                            });
 
-                                        const isInitialItem = !initialSearchKeys.current.length || initialSearchKeys.current.includes(item.key);
+                                            const isInitialItem = !initialSearchKeys.current.length || initialSearchKeys.current.includes(item.key);
 
-                                        return (
-                                            <Animated.View
-                                                key={item.translationPath}
-                                                entering={!isInitialItem ? FadeIn : undefined}
-                                            >
-                                                <MenuItem
-                                                    key={item.key}
-                                                    disabled={false}
-                                                    interactive
-                                                    title={translate(item.translationPath)}
-                                                    icon={item.icon}
-                                                    iconWidth={variables.iconSizeNormal}
-                                                    iconHeight={variables.iconSizeNormal}
-                                                    wrapperStyle={styles.sectionMenuItem}
-                                                    focused={focused}
-                                                    onPress={onPress}
-                                                    shouldIconUseAutoWidthStyle
-                                                />
-                                            </Animated.View>
-                                        );
-                                    })}
-                                </>
-                            )}
-                        </View>
-                    ))}
-                </View>
+                                            return (
+                                                <Animated.View
+                                                    key={item.translationPath}
+                                                    entering={!isInitialItem ? FadeIn : undefined}
+                                                >
+                                                    <MenuItem
+                                                        key={item.key}
+                                                        disabled={false}
+                                                        interactive
+                                                        title={translate(item.translationPath)}
+                                                        icon={item.icon}
+                                                        iconWidth={variables.iconSizeNormal}
+                                                        iconHeight={variables.iconSizeNormal}
+                                                        wrapperStyle={styles.sectionMenuItem}
+                                                        focused={focused}
+                                                        onPress={onPress}
+                                                        shouldIconUseAutoWidthStyle
+                                                    />
+                                                </Animated.View>
+                                            );
+                                        })}
+                                    </>
+                                )}
+                            </View>
+                        ))}
+                    </View>
+                )}
             </ScrollView>
         </>
     );
diff --git a/src/pages/Search/SuggestedSearchSkeleton.tsx b/src/pages/Search/SuggestedSearchSkeleton.tsx
new file mode 100644
index 000000000000..e128504b3de4
--- /dev/null
+++ b/src/pages/Search/SuggestedSearchSkeleton.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import {View} from 'react-native';
+import {Rect} from 'react-native-svg';
+import ItemListSkeletonView from '@components/Skeletons/ItemListSkeletonView';
+import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+const SUGGESTED_SEARCH_SKELETON_TEST_ID = 'SuggestedSearchSkeleton';
+const NAV_ITEM_HEIGHT = 52;
+
+/** ---- Relative layout tokens ---- */
+const LHN = {
+    icon: {xVal: 18, yVal: 8, w: 16, h: 16, r: 4},
+    // label is positioned relative to icon
+    label: {dx: 30, dy: 4, w: 104, h: 8},
+    // header bar in the small loader above each group
+    header: {xVal: 8, yVal: 0, w: 36, h: 4},
+};
+
+function SuggestedSearchSkeleton() {
+    const theme = useTheme();
+    const styles = useThemeStyles();
+    const {shouldUseNarrowLayout} = useResponsiveLayout();
+
+    const renderNavigationItem = () => {
+        const {icon, label} = LHN;
+        const labelX = icon.xVal + label.dx;
+        const labelY = icon.yVal + label.dy;
+
+        return (
+            <>
+                <Rect
+                    rx={icon.r}
+                    ry={icon.r}
+                    width={icon.w}
+                    height={icon.h}
+                    transform={[{translateX: icon.xVal}, {translateY: icon.yVal}]}
+                />
+                <Rect
+                    width={label.w}
+                    height={label.h}
+                    transform={[{translateX: labelX}, {translateY: labelY}]}
+                />
+            </>
+        );
+    };
+
+    const shouldRenderNavigationColumn = !shouldUseNarrowLayout;
+
+    const navigationColumnGroup = (
+        <>
+            <SkeletonViewContentLoader
+                animate
+                height={24}
+                backgroundColor={theme.skeletonLHNIn}
+                foregroundColor={theme.skeletonLHNOut}
+                style={[styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]}
+            >
+                <Rect
+                    width={LHN.header.w}
+                    height={LHN.header.h}
+                    transform={[{translateX: LHN.header.xVal}, {translateY: LHN.header.yVal}]}
+                />
+            </SkeletonViewContentLoader>
+
+            <ItemListSkeletonView
+                fixedNumItems={3}
+                itemViewHeight={NAV_ITEM_HEIGHT}
+                style={[styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]}
+                renderSkeletonItem={renderNavigationItem}
+            />
+        </>
+    );
+
+    const navigationColumn = shouldRenderNavigationColumn ? (
+        <View>
+            <View>
+                {[0, 1, 2].map((i) => (
+                    <View
+                        key={i}
+                        style={[styles.mb4]}
+                    >
+                        {navigationColumnGroup}
+                    </View>
+                ))}
+            </View>
+        </View>
+    ) : null;
+
+    return (
+        <View
+            testID={SUGGESTED_SEARCH_SKELETON_TEST_ID}
+            style={[styles.flex1, styles.flexRow, styles.pb5, styles.pt5, styles.gap4, styles.pl4]}
+        >
+            {navigationColumn}
+        </View>
+    );
+}
+
+SuggestedSearchSkeleton.displayName = 'SuggestedSearchSkeleton';
+export default SuggestedSearchSkeleton;
diff --git a/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts b/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts
new file mode 100644
index 000000000000..58042cfac1cb
--- /dev/null
+++ b/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts
@@ -0,0 +1,196 @@
+import {act, renderHook} from '@testing-library/react-native';
+import useSuggestedSearchDefaultNavigation from '@hooks/useSuggestedSearchDefaultNavigation';
+import {clearAllFilters} from '@libs/actions/Search';
+import Navigation from '@libs/Navigation/Navigation';
+import type {SearchTypeMenuItem} from '@libs/SearchUIUtils';
+import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
+import ROUTES from '@src/ROUTES';
+import type IconAsset from '@src/types/utils/IconAsset';
+import { buildQueryStringFromFilterFormValues } from '@libs/SearchQueryUtils';
+
+jest.mock('@libs/actions/Search', () => ({
+    clearAllFilters: jest.fn(),
+}));
+
+jest.mock('@libs/Navigation/Navigation', () => ({
+    navigate: jest.fn(),
+}));
+
+function createApproveMenuItem(): SearchTypeMenuItem {
+    return {
+        key: CONST.SEARCH.SEARCH_KEYS.APPROVE,
+        translationPath: 'common.approve' as TranslationPaths,
+        type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+        icon: {} as IconAsset,
+        searchQuery: buildQueryStringFromFilterFormValues({
+            type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+            action: CONST.SEARCH.ACTION_FILTERS.APPROVE,
+        }),
+        searchQueryJSON: undefined,
+        hash: 1,
+        similarSearchHash: 101,
+    };
+}
+
+function createSubmitMenuItem(): SearchTypeMenuItem {
+    return {
+        key: CONST.SEARCH.SEARCH_KEYS.SUBMIT,
+        translationPath: 'iou.submitExpense' as TranslationPaths,
+        type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+        icon: {} as IconAsset,
+        searchQuery: buildQueryStringFromFilterFormValues({
+            type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+            action: CONST.SEARCH.ACTION_FILTERS.SUBMIT,
+        }),
+        searchQueryJSON: undefined,
+        hash: 2,
+        similarSearchHash: 202,
+    };
+}
+
+function createExpenseMenuItem(): SearchTypeMenuItem {
+    return {
+        key: CONST.SEARCH.SEARCH_KEYS.EXPENSES,
+        translationPath: 'common.expenses' as TranslationPaths,
+        type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+        icon: {} as IconAsset,
+        searchQuery: buildQueryStringFromFilterFormValues({
+            type: CONST.SEARCH.DATA_TYPES.EXPENSE,
+        }),
+        searchQueryJSON: undefined,
+        hash: 3,
+        similarSearchHash: 303,
+    };
+}
+
+function createExpenseReportMenuItem(): SearchTypeMenuItem {
+    return {
+        key: CONST.SEARCH.SEARCH_KEYS.REPORTS,
+        translationPath: 'common.reports' as TranslationPaths,
+        type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+        icon: {} as IconAsset,
+        searchQuery: buildQueryStringFromFilterFormValues({
+            type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
+        }),
+        searchQueryJSON: undefined,
+        hash: 4,
+        similarSearchHash: 404,
+    };
+}
+
+function createChatMenuItem(): SearchTypeMenuItem {
+    return {
+        key: CONST.SEARCH.SEARCH_KEYS.CHATS,
+        translationPath: 'common.chats' as TranslationPaths,
+        type: CONST.SEARCH.DATA_TYPES.CHAT,
+        icon: {} as IconAsset,
+        searchQuery: buildQueryStringFromFilterFormValues({
+            type: CONST.SEARCH.DATA_TYPES.CHAT,
+        }),
+        searchQueryJSON: undefined,
+        hash: 5,
+        similarSearchHash: 505,
+    };
+}
+
+describe('useSuggestedSearchDefaultNavigation', () => {
+    beforeEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it('navigates to the Approve search when skeleton hides and approve menu item is available', () => {
+        const clearSelectedTransactions = jest.fn();
+        const approveMenuItem = createApproveMenuItem();
+        const submitMenuItem = createSubmitMenuItem();
+        const expenseMenuItem = createExpenseMenuItem();
+        const expenseReportMenuItem = createExpenseReportMenuItem();
+        const chatMenuItem = createChatMenuItem();
+
+        const {rerender} = renderHook((props: Parameters<typeof useSuggestedSearchDefaultNavigation>[0]) => useSuggestedSearchDefaultNavigation(props), {
+            initialProps: {
+                shouldShowSkeleton: true,
+                flattenedMenuItems: [approveMenuItem, submitMenuItem, expenseMenuItem, expenseReportMenuItem, chatMenuItem],
+                similarSearchHash: undefined,
+                clearSelectedTransactions,
+            },
+        });
+
+        rerender({
+            shouldShowSkeleton: false,
+            flattenedMenuItems: [approveMenuItem, submitMenuItem, expenseMenuItem, expenseReportMenuItem, chatMenuItem],
+            similarSearchHash: undefined,
+            clearSelectedTransactions,
+        });
+
+        expect(clearAllFilters).toHaveBeenCalledTimes(1);
+        expect(clearSelectedTransactions).toHaveBeenCalledTimes(1);
+        expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.SEARCH_ROOT.getRoute({query: approveMenuItem.searchQuery}));
+    });
+
+    it('goes to Submit search when Approve is unavailable and Submit menu item is available', () => {
+        const clearSelectedTransactions = jest.fn();
+        const submitMenuItem = createSubmitMenuItem();
+        const expenseMenuItem = createExpenseMenuItem();
+        const expenseReportMenuItem = createExpenseReportMenuItem();
+        const chatMenuItem = createChatMenuItem();
+
+        const {rerender} = renderHook((props: Parameters<typeof useSuggestedSearchDefaultNavigation>[0]) => useSuggestedSearchDefaultNavigation(props), {
+            initialProps: {
+                shouldShowSkeleton: true,
+                flattenedMenuItems: [submitMenuItem, expenseMenuItem, expenseReportMenuItem, chatMenuItem],
+                similarSearchHash: undefined,
+                clearSelectedTransactions,
+            },
+        });
+
+        rerender({
+            shouldShowSkeleton: false,
+            flattenedMenuItems: [submitMenuItem, expenseMenuItem, expenseReportMenuItem, chatMenuItem],
+            similarSearchHash: undefined,
+            clearSelectedTransactions,
+        });
+
+        expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.SEARCH_ROOT.getRoute({query: submitMenuItem.searchQuery}));
+    });
+
+    it('does not navigate if skeleton never rendered', () => {
+        const clearSelectedTransactions = jest.fn();
+        const approveMenuItem = createApproveMenuItem();
+        const submitMenuItem = createSubmitMenuItem();
+        const expenseMenuItem = createExpenseMenuItem();
+        const expenseReportMenuItem = createExpenseReportMenuItem();
+        const chatMenuItem = createChatMenuItem();
+        renderHook((props: Parameters<typeof useSuggestedSearchDefaultNavigation>[0]) => useSuggestedSearchDefaultNavigation(props), {
+            initialProps: {
+                shouldShowSkeleton: false,
+                flattenedMenuItems: [approveMenuItem, submitMenuItem, expenseMenuItem, expenseReportMenuItem, chatMenuItem],
+                similarSearchHash: undefined,
+                clearSelectedTransactions,
+            },
+        });
+
+        expect(clearAllFilters).not.toHaveBeenCalled();
+        expect(Navigation.navigate).not.toHaveBeenCalled();
+    });
+
+    it('does not navigate if search already active', () => {
+        const clearSelectedTransactions = jest.fn();
+        const approveMenuItem = createApproveMenuItem();
+        const submitMenuItem = createSubmitMenuItem();
+        const expenseMenuItem = createExpenseMenuItem();
+        const expenseReportMenuItem = createExpenseReportMenuItem();
+        const chatMenuItem = createChatMenuItem();
+        renderHook((props: Parameters<typeof useSuggestedSearchDefaultNavigation>[0]) => useSuggestedSearchDefaultNavigation(props), {
+            initialProps: {
+                shouldShowSkeleton: false,
+                flattenedMenuItems: [approveMenuItem, submitMenuItem, expenseMenuItem, expenseReportMenuItem, chatMenuItem],
+                similarSearchHash: expenseMenuItem.similarSearchHash,
+                clearSelectedTransactions,
+            },
+        });
+
+        expect(clearAllFilters).not.toHaveBeenCalled();
+        expect(Navigation.navigate).not.toHaveBeenCalled();
+    });
+});
diff --git a/tests/unit/useSearchTypeMenuSectionsTest.ts b/tests/unit/useSearchTypeMenuSectionsTest.ts
new file mode 100644
index 000000000000..a188d53bdb53
--- /dev/null
+++ b/tests/unit/useSearchTypeMenuSectionsTest.ts
@@ -0,0 +1,123 @@
+import {renderHook} from '@testing-library/react-native';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Policy} from '@src/types/onyx';
+
+const mockCreateTypeMenuSections = jest.fn(() => []);
+
+jest.mock('@libs/SearchUIUtils', () => ({
+    createTypeMenuSections: (...args: unknown[]) => mockCreateTypeMenuSections(...args),
+}));
+
+jest.mock('@libs/ReportUtils', () => ({
+    getPersonalDetailsForAccountID: jest.fn(),
+    hasEmptyReportsForPolicy: jest.fn(() => false),
+    hasViolations: jest.fn(() => false),
+}));
+
+jest.mock('@userActions/Report', () => ({
+    createNewReport: jest.fn(() => ({reportID: 'mock-report-id'})),
+}));
+
+jest.mock('@hooks/useCardFeedsForDisplay', () => jest.fn(() => ({defaultCardFeed: null, cardFeedsByPolicy: {}, defaultExpensifyCard: null})));
+jest.mock('@hooks/useCreateEmptyReportConfirmation', () => jest.fn(() => ({openCreateReportConfirmation: jest.fn(), CreateReportConfirmationModal: null})));
+jest.mock('@hooks/useNetwork', () => jest.fn(() => ({isOffline: false})));
+jest.mock('@hooks/usePermissions', () => jest.fn(() => ({isBetaEnabled: jest.fn(() => false)})));
+
+const onyxData: Record<string, unknown> = {};
+
+const mockUseOnyx = jest.fn(
+    (
+        key: string,
+        options?: {
+            selector?: (value: unknown) => unknown;
+        },
+    ) => {
+        const value = onyxData[key];
+        const selectedValue = options?.selector ? options.selector(value as never) : value;
+        return [selectedValue];
+    },
+);
+
+jest.mock('@hooks/useOnyx', () => ({
+    __esModule: true,
+    default: (key: string, options?: {selector?: (value: unknown) => unknown}) => mockUseOnyx(key, options),
+}));
+
+jest.mock('@selectors/Policy', () => ({
+    createPoliciesSelector: jest.fn((policies: OnyxCollection<Policy>, policySelector: (policy: OnyxEntry<Policy>) => OnyxEntry<Policy>) => {
+        if (!policies) {
+            return policies;
+        }
+
+        return Object.fromEntries(Object.entries(policies).map(([policyKey, policyValue]) => [policyKey, policyValue ? policySelector(policyValue) : policyValue]));
+    }),
+}));
+
+describe('useSearchTypeMenuSections', () => {
+    beforeEach(() => {
+        onyxData[ONYXKEYS.COLLECTION.POLICY] = {};
+        onyxData[ONYXKEYS.SESSION] = {email: 'test@example.com', accountID: 1};
+        onyxData[ONYXKEYS.NVP_ACTIVE_POLICY_ID] = undefined;
+        onyxData[ONYXKEYS.SAVED_SEARCHES] = {};
+        onyxData[ONYXKEYS.COLLECTION.REPORT] = {};
+        onyxData[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS] = {};
+
+        mockUseOnyx.mockClear();
+        mockCreateTypeMenuSections.mockClear();
+    });
+
+    it('does not show suggested search skeleton when no policies exist', () => {
+        const {result} = renderHook(() => useSearchTypeMenuSections());
+
+        expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false);
+    });
+
+    it('shows suggested search skeleton when policies are missing employeeList', () => {
+        onyxData[ONYXKEYS.COLLECTION.POLICY] = {
+            policy1: {
+                id: 'policy1',
+                employeeList: undefined,
+                exporter: 'test@gmail.com',
+            },
+        };
+
+        const {result} = renderHook(() => useSearchTypeMenuSections());
+
+        expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(true);
+    });
+
+    it('shows suggested search skeleton when policies are missing exporter', () => {
+        onyxData[ONYXKEYS.COLLECTION.POLICY] = {
+            policy1: {
+                id: 'policy1',
+                employeeList: {'test@gmail.com': {accountID: 10000}},
+                exporter: undefined,
+            },
+        };
+
+        const {result} = renderHook(() => useSearchTypeMenuSections());
+
+        expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(true);
+    });
+
+    it('hides suggested search skeleton when at least one policy has required data', () => {
+        onyxData[ONYXKEYS.COLLECTION.POLICY] = {
+            policy1: {
+                id: 'policy1',
+                employeeList: {'test@gmail.com': {accountID: 10000}},
+                exporter: '',
+            },
+            policy2: {
+                id: 'policy2',
+                employeeList: undefined,
+                exporter: undefined,
+            },
+        };
+
+        const {result} = renderHook(() => useSearchTypeMenuSections());
+
+        expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false);
+    });
+});

From 3ecf2d633bc03de46e97351cb656b541763b35f8 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 3 Nov 2025 18:00:47 +0530
Subject: [PATCH 02/16] Update tests

---
 .../hooks/useSuggestedSearchDefaultNavigationTest.ts  |  4 ++--
 tests/unit/useSearchTypeMenuSectionsTest.ts           | 11 ++---------
 2 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts b/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts
index 58042cfac1cb..b8d56fbba1b1 100644
--- a/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts
+++ b/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts
@@ -1,13 +1,13 @@
-import {act, renderHook} from '@testing-library/react-native';
+import {renderHook} from '@testing-library/react-native';
 import useSuggestedSearchDefaultNavigation from '@hooks/useSuggestedSearchDefaultNavigation';
 import {clearAllFilters} from '@libs/actions/Search';
 import Navigation from '@libs/Navigation/Navigation';
+import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils';
 import type {SearchTypeMenuItem} from '@libs/SearchUIUtils';
 import CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
 import ROUTES from '@src/ROUTES';
 import type IconAsset from '@src/types/utils/IconAsset';
-import { buildQueryStringFromFilterFormValues } from '@libs/SearchQueryUtils';
 
 jest.mock('@libs/actions/Search', () => ({
     clearAllFilters: jest.fn(),
diff --git a/tests/unit/useSearchTypeMenuSectionsTest.ts b/tests/unit/useSearchTypeMenuSectionsTest.ts
index a188d53bdb53..7332e39bacab 100644
--- a/tests/unit/useSearchTypeMenuSectionsTest.ts
+++ b/tests/unit/useSearchTypeMenuSectionsTest.ts
@@ -1,15 +1,11 @@
+/* eslint-disable @typescript-eslint/naming-convention */
+
 import {renderHook} from '@testing-library/react-native';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Policy} from '@src/types/onyx';
 
-const mockCreateTypeMenuSections = jest.fn(() => []);
-
-jest.mock('@libs/SearchUIUtils', () => ({
-    createTypeMenuSections: (...args: unknown[]) => mockCreateTypeMenuSections(...args),
-}));
-
 jest.mock('@libs/ReportUtils', () => ({
     getPersonalDetailsForAccountID: jest.fn(),
     hasEmptyReportsForPolicy: jest.fn(() => false),
@@ -59,13 +55,10 @@ describe('useSearchTypeMenuSections', () => {
     beforeEach(() => {
         onyxData[ONYXKEYS.COLLECTION.POLICY] = {};
         onyxData[ONYXKEYS.SESSION] = {email: 'test@example.com', accountID: 1};
-        onyxData[ONYXKEYS.NVP_ACTIVE_POLICY_ID] = undefined;
         onyxData[ONYXKEYS.SAVED_SEARCHES] = {};
         onyxData[ONYXKEYS.COLLECTION.REPORT] = {};
-        onyxData[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS] = {};
 
         mockUseOnyx.mockClear();
-        mockCreateTypeMenuSections.mockClear();
     });
 
     it('does not show suggested search skeleton when no policies exist', () => {

From b40a2983e29ac7a78370166ca42c2c1bf69dd1bf Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Tue, 4 Nov 2025 20:08:35 +0530
Subject: [PATCH 03/16] Show loading UI when no policies have loaded

---
 src/hooks/useSearchTypeMenuSections.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/hooks/useSearchTypeMenuSections.ts b/src/hooks/useSearchTypeMenuSections.ts
index 67b92c3fec6b..a0d537d4a971 100644
--- a/src/hooks/useSearchTypeMenuSections.ts
+++ b/src/hooks/useSearchTypeMenuSections.ts
@@ -116,10 +116,6 @@ const useSearchTypeMenuSections = () => {
     const isSuggestedSearchDataReady = useMemo(() => {
         const policiesList = Object.values(allPolicies ?? {}).filter((policy): policy is NonNullable<typeof policy> => policy !== null && policy !== undefined);
 
-        if (policiesList.length === 0) {
-            return true;
-        }
-
         return policiesList.some((policy) => policy.employeeList !== undefined && policy.exporter !== undefined);
     }, [allPolicies]);
 

From b22b467464c86e75b0c59f1965e278f4efcf621d Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Tue, 4 Nov 2025 20:10:43 +0530
Subject: [PATCH 04/16] Prettier fixes

---
 tests/unit/useSearchTypeMenuSectionsTest.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/unit/useSearchTypeMenuSectionsTest.ts b/tests/unit/useSearchTypeMenuSectionsTest.ts
index 7332e39bacab..02f38378a6ce 100644
--- a/tests/unit/useSearchTypeMenuSectionsTest.ts
+++ b/tests/unit/useSearchTypeMenuSectionsTest.ts
@@ -1,5 +1,4 @@
 /* eslint-disable @typescript-eslint/naming-convention */
-
 import {renderHook} from '@testing-library/react-native';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections';

From c06e3d81d9cfd8099ba85332ace1fec0f90a3fe4 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Tue, 4 Nov 2025 22:14:41 +0530
Subject: [PATCH 05/16] Update useSearchTypeMenuSectionsTest

---
 tests/unit/useSearchTypeMenuSectionsTest.ts | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/tests/unit/useSearchTypeMenuSectionsTest.ts b/tests/unit/useSearchTypeMenuSectionsTest.ts
index 02f38378a6ce..d1c4289c4fb5 100644
--- a/tests/unit/useSearchTypeMenuSectionsTest.ts
+++ b/tests/unit/useSearchTypeMenuSectionsTest.ts
@@ -60,12 +60,6 @@ describe('useSearchTypeMenuSections', () => {
         mockUseOnyx.mockClear();
     });
 
-    it('does not show suggested search skeleton when no policies exist', () => {
-        const {result} = renderHook(() => useSearchTypeMenuSections());
-
-        expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false);
-    });
-
     it('shows suggested search skeleton when policies are missing employeeList', () => {
         onyxData[ONYXKEYS.COLLECTION.POLICY] = {
             policy1: {

From 71fc278e6a4698080723e1f84ad735f57924a184 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Wed, 5 Nov 2025 20:13:19 +0530
Subject: [PATCH 06/16] Handle user offline scenario

---
 src/hooks/useSearchTypeMenuSections.ts      |  2 +-
 tests/unit/useSearchTypeMenuSectionsTest.ts | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/hooks/useSearchTypeMenuSections.ts b/src/hooks/useSearchTypeMenuSections.ts
index a0d537d4a971..7c1df75f7aee 100644
--- a/src/hooks/useSearchTypeMenuSections.ts
+++ b/src/hooks/useSearchTypeMenuSections.ts
@@ -154,7 +154,7 @@ const useSearchTypeMenuSections = () => {
     return {
         typeMenuSections,
         CreateReportConfirmationModal,
-        shouldShowSuggestedSearchSkeleton: !isSuggestedSearchDataReady,
+        shouldShowSuggestedSearchSkeleton: !isSuggestedSearchDataReady && !isOffline,
     };
 };
 
diff --git a/tests/unit/useSearchTypeMenuSectionsTest.ts b/tests/unit/useSearchTypeMenuSectionsTest.ts
index d1c4289c4fb5..0047d6188cd5 100644
--- a/tests/unit/useSearchTypeMenuSectionsTest.ts
+++ b/tests/unit/useSearchTypeMenuSectionsTest.ts
@@ -1,6 +1,7 @@
 /* eslint-disable @typescript-eslint/naming-convention */
 import {renderHook} from '@testing-library/react-native';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import useNetwork from '@hooks/useNetwork';
 import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Policy} from '@src/types/onyx';
@@ -106,4 +107,13 @@ describe('useSearchTypeMenuSections', () => {
 
         expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false);
     });
+
+    it('does not show suggested search skeleton when offline', () => {
+        (useNetwork as jest.Mock).mockReturnValue({
+            isOffline: true,
+        });
+        const {result} = renderHook(() => useSearchTypeMenuSections());
+
+        expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false);
+    });
 });

From b61e5dd606b258f8abad2eb7c99a809b777ebac3 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Thu, 6 Nov 2025 01:40:02 +0530
Subject: [PATCH 07/16] Add prevIsOffline to deps

---
 src/components/Search/index.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 3298ad78fb77..fb51fe2ea07f 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -433,7 +433,7 @@ function Search({
         // We don't need to run the effect on change of isFocused.
         // eslint-disable-next-line react-compiler/react-compiler
         // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [handleSearch, isOffline, offset, queryJSON, searchKey, shouldCalculateTotals]);
+    }, [handleSearch, prevIsOffline, offset, queryJSON, searchKey, shouldCalculateTotals]);
 
     // When new data load, selectedTransactions is updated in next effect. We use this flag to whether selection is updated
     const isRefreshingSelection = useRef(false);

From 065d45a84e810f8360351289b2f075b86c3b5fae Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Sun, 9 Nov 2025 03:56:02 +0530
Subject: [PATCH 08/16] Handle issue of extra API calls

---
 src/hooks/useSearchHighlightAndScroll.ts | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts
index 5d0a1ca2a568..7752bce0d1c8 100644
--- a/src/hooks/useSearchHighlightAndScroll.ts
+++ b/src/hooks/useSearchHighlightAndScroll.ts
@@ -50,6 +50,7 @@ function useSearchHighlightAndScroll({
     const highlightedIDs = useRef<Set<string>>(new Set());
     const initializedRef = useRef(false);
     const hasPendingSearchRef = useRef(false);
+    const hasProcessedInitialChangeRef = useRef(false);
     const isChat = queryJSON.type === CONST.SEARCH.DATA_TYPES.CHAT;
 
     const existingSearchResultIDs = useMemo(() => {
@@ -80,6 +81,15 @@ function useSearchHighlightAndScroll({
 
     // Trigger search when a new report action is added while on chat or when a new transaction is added for the other search types.
     useEffect(() => {
+        if (!initializedRef.current) {
+            return;
+        }
+
+        if (!hasProcessedInitialChangeRef.current) {
+            hasProcessedInitialChangeRef.current = true;
+            return;
+        }
+
         const previousTransactionsIDs = Object.keys(previousTransactions ?? {});
         const transactionsIDs = Object.keys(transactions ?? {});
 
@@ -164,6 +174,7 @@ function useSearchHighlightAndScroll({
 
         highlightedIDs.current = new Set(existingSearchResultIDs);
         initializedRef.current = true;
+        hasProcessedInitialChangeRef.current = false;
     }, [searchResults?.data, isChat, existingSearchResultIDs]);
 
     // Detect new items (transactions or report actions)

From 6c13582e9fdfad9dc48a4df72abc58f27f7a928e Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 10 Nov 2025 02:03:20 +0530
Subject: [PATCH 09/16] Update

---
 src/components/Search/index.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index fb51fe2ea07f..3298ad78fb77 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -433,7 +433,7 @@ function Search({
         // We don't need to run the effect on change of isFocused.
         // eslint-disable-next-line react-compiler/react-compiler
         // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [handleSearch, prevIsOffline, offset, queryJSON, searchKey, shouldCalculateTotals]);
+    }, [handleSearch, isOffline, offset, queryJSON, searchKey, shouldCalculateTotals]);
 
     // When new data load, selectedTransactions is updated in next effect. We use this flag to whether selection is updated
     const isRefreshingSelection = useRef(false);

From ce142104bf0073c62c7a89a8217bd138ea78f827 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 10 Nov 2025 21:41:01 +0530
Subject: [PATCH 10/16] Update

---
 src/components/Search/index.tsx          | 11 ++++--
 src/hooks/useSearchHighlightAndScroll.ts | 50 +++++++++++-------------
 2 files changed, 29 insertions(+), 32 deletions(-)

diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 3298ad78fb77..7bc4b815b0c0 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -370,6 +370,13 @@ function Search({
         openSearch();
     }, []);
 
+    useEffect(() => {
+        if (!prevIsOffline || isOffline) {
+            return;
+        }
+        openSearch();
+    }, [isOffline, prevIsOffline]);
+
     const {newSearchResultKey, handleSelectionListScroll, newTransactions} = useSearchHighlightAndScroll({
         searchResults,
         transactions,
@@ -424,10 +431,6 @@ function Search({
             return;
         }
 
-        if (prevIsOffline && !isOffline) {
-            openSearch();
-        }
-
         handleSearch({queryJSON, searchKey, offset, shouldCalculateTotals, prevReportsLength: dataLength});
 
         // We don't need to run the effect on change of isFocused.
diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts
index 7752bce0d1c8..8cb73b30ea8e 100644
--- a/src/hooks/useSearchHighlightAndScroll.ts
+++ b/src/hooks/useSearchHighlightAndScroll.ts
@@ -46,11 +46,10 @@ function useSearchHighlightAndScroll({
     const searchTriggeredRef = useRef(false);
     const hasNewItemsRef = useRef(false);
     const previousSearchResults = usePrevious(searchResults?.data);
-    const [newSearchResultKey, setNewSearchResultKey] = useState<string | null>(null);
+    const [newSearchResultKeys, setNewSearchResultKeys] = useState<Set<string> | null>(null);
     const highlightedIDs = useRef<Set<string>>(new Set());
     const initializedRef = useRef(false);
     const hasPendingSearchRef = useRef(false);
-    const hasProcessedInitialChangeRef = useRef(false);
     const isChat = queryJSON.type === CONST.SEARCH.DATA_TYPES.CHAT;
 
     const existingSearchResultIDs = useMemo(() => {
@@ -81,15 +80,6 @@ function useSearchHighlightAndScroll({
 
     // Trigger search when a new report action is added while on chat or when a new transaction is added for the other search types.
     useEffect(() => {
-        if (!initializedRef.current) {
-            return;
-        }
-
-        if (!hasProcessedInitialChangeRef.current) {
-            hasProcessedInitialChangeRef.current = true;
-            return;
-        }
-
         const previousTransactionsIDs = Object.keys(previousTransactions ?? {});
         const transactionsIDs = Object.keys(transactions ?? {});
 
@@ -174,7 +164,6 @@ function useSearchHighlightAndScroll({
 
         highlightedIDs.current = new Set(existingSearchResultIDs);
         initializedRef.current = true;
-        hasProcessedInitialChangeRef.current = false;
     }, [searchResults?.data, isChat, existingSearchResultIDs]);
 
     // Detect new items (transactions or report actions)
@@ -193,11 +182,13 @@ function useSearchHighlightAndScroll({
                 return;
             }
 
-            const newReportActionID = newReportActionIDs.at(0) ?? '';
-            const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportActionID}`;
-
-            setNewSearchResultKey(newReportActionKey);
-            highlightedIDs.current.add(newReportActionID);
+            const newKeys = new Set<string>();
+            newReportActionIDs.forEach((id) => {
+                const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${id}`;
+                highlightedIDs.current.add(newReportActionKey);
+                newKeys.add(newReportActionKey);
+            });
+            setNewSearchResultKeys(newKeys);
         } else {
             const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults);
             const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data);
@@ -209,26 +200,28 @@ function useSearchHighlightAndScroll({
                 return;
             }
 
-            const newTransactionID = newTransactionIDs.at(0) ?? '';
-            const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`;
-
-            setNewSearchResultKey(newTransactionKey);
-            highlightedIDs.current.add(newTransactionID);
+            const newKeys = new Set<string>();
+            newTransactionIDs.forEach((id) => {
+                const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${id}`;
+                highlightedIDs.current.add(newTransactionKey);
+                newKeys.add(newTransactionKey);
+            });
+            setNewSearchResultKeys(newKeys);
         }
     }, [searchResults?.data, previousSearchResults, isChat]);
 
     // Reset newSearchResultKey after it's been used
     useEffect(() => {
-        if (newSearchResultKey === null) {
+        if (newSearchResultKeys === null) {
             return;
         }
 
         const timer = setTimeout(() => {
-            setNewSearchResultKey(null);
+            setNewSearchResultKeys(null);
         }, CONST.ANIMATED_HIGHLIGHT_START_DURATION);
 
         return () => clearTimeout(timer);
-    }, [newSearchResultKey]);
+    }, [newSearchResultKeys]);
 
     /**
      * Callback to handle scrolling to the new search result.
@@ -237,7 +230,8 @@ function useSearchHighlightAndScroll({
         (data: SearchListItem[], ref: SelectionListHandle | null) => {
             // Early return if there's no ref, new transaction wasn't brought in by this hook
             // or there's no new search result key
-            if (!ref || !triggeredByHookRef.current || newSearchResultKey === null) {
+            const newSearchResultKey = newSearchResultKeys?.values().next().value;
+            if (!ref || !triggeredByHookRef.current || !newSearchResultKey) {
                 return;
             }
 
@@ -275,10 +269,10 @@ function useSearchHighlightAndScroll({
             // Reset the trigger flag to prevent unintended future scrolls and highlights
             triggeredByHookRef.current = false;
         },
-        [newSearchResultKey, isChat],
+        [newSearchResultKeys, isChat],
     );
 
-    return {newSearchResultKey, handleSelectionListScroll, newTransactions};
+    return {newSearchResultKeys, handleSelectionListScroll, newTransactions};
 }
 
 /**

From f2de5646fa96f6c95a949298f1b6f2d49bdb46c8 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Tue, 11 Nov 2025 12:09:31 +0530
Subject: [PATCH 11/16] Merge main

---
 manual-tests-pr-72034.md | 62 ----------------------------------------
 1 file changed, 62 deletions(-)
 delete mode 100644 manual-tests-pr-72034.md

diff --git a/manual-tests-pr-72034.md b/manual-tests-pr-72034.md
deleted file mode 100644
index 435321b1e919..000000000000
--- a/manual-tests-pr-72034.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# Manual Tests Derived from PR #72034 Comments
-
-Each test below comes directly from feedback left on [PR #72034](https://github.com/Expensify/App/pull/72034) and should be added to the PR description so reviewers can verify the scenarios that were called out during review.
-
-## 1. Search skeleton alignment on wide layouts
-- **Source:** https://github.com/Expensify/App/pull/72034#issuecomment-3407040487
-1. Sign in on desktop web (Chrome) with a workspace admin account.
-2. Go to `Reports` and open the Search page.
-3. In DevTools, throttle the network to `Slow 3G` and reload so the new LHN skeleton is visible.
-4. Observe the placeholder rows while data is still loading.
-- **Expected:** Every skeleton row aligns with the eventual menu layout (left padding, height, spacing). No row appears offset or clipped on wide screens.
-
-## 2. iOS menu update after live workspace invite
-- **Source:** https://github.com/Expensify/App/pull/72034#issuecomment-3417680190
-1. Log in to the iOS hybrid app with Account A.
-2. From Account B (desktop), invite Account A to an existing workspace with submit permission while Account A stays logged in.
-3. After the invite processes, open the reports Search menu on Account A.
-4. Tap the newly added `Submit` option.
-- **Expected:** The `Submit` row appears without requiring an app restart, and tapping it opens the correct modal instead of a blank/blocked sheet.
-
-## 3. Highlight resets when a new default option appears
-- **Source:** https://github.com/Expensify/App/pull/72034#issuecomment-3417682412
-1. Start as a workspace member (no approve permission) on any platform.
-2. Open the Search menu and note that `Submit` is the default highlighted entry.
-3. Without reloading, promote this user to an approver from another admin account so the `Approve` entry becomes available.
-4. Re-open the Search menu.
-- **Expected:** Only one entry is highlighted—the newly inserted default (`Approve`). The previously highlighted option is cleared so the UI does not show two simultaneous highlights.
-
-## 4. Default logic respects manual overrides vs. automatic defaults
-- **Sources:** https://github.com/Expensify/App/pull/72034#issuecomment-3417967886 and https://github.com/Expensify/App/pull/72034#issuecomment-3421359484
-1. As a non-approver, manually select a non-default tab (e.g., `Explore`).
-2. From another account, promote this user to an approver while they stay logged in.
-3. Open the Search menu before reloading, then log out and back in to test the fresh-login path.
-- **Expected:**
-   - In the existing session, the manually selected tab remains active (we do not override explicit user choice).
-   - After a fresh login, the default switches to `Approve` automatically if the user now has approval permission and never set a manual preference.
-
-## 5. Mobile popover while data is still loading
-- **Sources:** https://github.com/Expensify/App/pull/72034#issuecomment-3417688714 and https://github.com/Expensify/App/pull/72034#issuecomment-3421366059
-1. On a narrow viewport (iOS/Android hybrid app), throttle the connection to `Slow 3G`.
-2. Navigate to `Reports` and immediately open the bottom-docked SearchTypeMenu popover while the LHN skeleton is showing.
-- **Expected:** The popover presents a clear loading state (skeleton rows or disabled placeholders) instead of flashing stale data or an empty modal. Capture a video for design if the experience regresses.
-
-## 6. Slow-connection lag when opening the modal
-- **Sources:** https://github.com/Expensify/App/pull/72034#issuecomment-3421454469 and https://github.com/Expensify/App/pull/72034#issuecomment-3421608480
-1. On iOS (hybrid app) set the Network Link Conditioner to a very slow profile.
-2. Open the Search popover multiple times while data is fetching.
-- **Expected:** The modal waits for data before rendering options, but the app remains responsive (no locks or console errors). Any delay is communicated through the loader.
-
-## 7. Real-time update when workspace membership changes (removal as well as addition)
-- **Source:** https://github.com/Expensify/App/pull/72034#discussion_r2448015214
-1. With the app open on iOS, ensure the user currently has submit/approve access and see the corresponding entries in the menu.
-2. From another admin account on desktop, remove the user from the workspace (or revoke the permission).
-3. Watch the iOS device without reloading, then open the Search menu.
-- **Expected:** The `Submit`/`Approve` entries disappear (or get disabled) in real time and the menu no longer references permissions the user lost.
-
-## 8. Approve tab only appears after policy data is ready (employee list / exporter / reimburser)
-- **Source:** https://github.com/Expensify/App/pull/72034#discussion_r2460924733
-1. Use a workspace that has not finished syncing `employeeList`, `exporter`, or `reimburser` data (e.g., immediately after enabling payments).
-2. Log in and open the Search menu before those Onyx keys load.
-3. Wait for the policy data to arrive (confirm via logs or Onyx inspector) without reloading.
-- **Expected:** The `Approve` tab stays hidden until all required policy data is available, then automatically appears once the data finishes loading.

From fc1fb2bdbd2428db02301e193cdcb769fc04ed11 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Wed, 12 Nov 2025 00:59:48 +0530
Subject: [PATCH 12/16] Update horizontal margin for the loading UI

---
 src/pages/Search/SearchTypeMenu.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 722cabac0531..5df36d9014b5 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -228,7 +228,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
                 showsVerticalScrollIndicator={false}
             >
                 {shouldShowSuggestedSearchSkeleton ? (
-                    <View style={[styles.pb4, styles.mh3]}>
+                    <View style={[styles.pb4]}>
                         <SuggestedSearchSkeleton />
                     </View>
                 ) : (

From fd4ce284a1e47817a7c3f3335ed622e8e9474714 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 17 Nov 2025 11:58:21 +0530
Subject: [PATCH 13/16] Improve design

---
 src/pages/Search/SearchTypeMenu.tsx          | 32 +++++++++------
 src/pages/Search/SuggestedSearchSkeleton.tsx | 42 +++++++++++++++-----
 2 files changed, 53 insertions(+), 21 deletions(-)

diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 5df36d9014b5..f19655f054cc 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -1,7 +1,7 @@
 import {useIsFocused, useRoute} from '@react-navigation/native';
 import {accountIDSelector} from '@selectors/Session';
 import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef} from 'react';
-import {View} from 'react-native';
+import {StyleSheet, View} from 'react-native';
 // eslint-disable-next-line no-restricted-imports
 import type {ScrollView as RNScrollView, ScrollViewProps} from 'react-native';
 import Animated, {FadeIn} from 'react-native-reanimated';
@@ -197,16 +197,23 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
     }, [createSavedSearchMenuItem, savedSearches]);
 
     const renderSavedSearchesSection = useCallback(
-        (menuItems: MenuItemWithLink[]) => (
-            <MenuItemList
-                menuItems={menuItems}
-                wrapperStyle={styles.sectionMenuItem}
-                icon={Expensicons.Bookmark}
-                iconWidth={variables.iconSizeNormal}
-                iconHeight={variables.iconSizeNormal}
-                shouldUseSingleExecution
-            />
-        ),
+        (menuItems: MenuItemWithLink[]) => {
+            const menuItemsWithIconSpacing = menuItems.map((menuItem) => ({
+                ...menuItem,
+                iconStyles: [menuItem.iconStyles, styles.mr3],
+            }));
+
+            return (
+                <MenuItemList
+                    menuItems={menuItemsWithIconSpacing}
+                    wrapperStyle={styles.sectionMenuItem}
+                    icon={Expensicons.Bookmark}
+                    iconWidth={variables.iconSizeNormal}
+                    iconHeight={variables.iconSizeNormal}
+                    shouldUseSingleExecution
+                />
+            );
+        },
         [styles],
     );
 
@@ -228,7 +235,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
                 showsVerticalScrollIndicator={false}
             >
                 {shouldShowSuggestedSearchSkeleton ? (
-                    <View style={[styles.pb4]}>
+                    <View style={[styles.pb4, styles.mh3, styles.gap4]}>
                         <SuggestedSearchSkeleton />
                     </View>
                 ) : (
@@ -273,6 +280,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
                                                         icon={item.icon}
                                                         iconWidth={variables.iconSizeNormal}
                                                         iconHeight={variables.iconSizeNormal}
+                                                        iconStyles={styles.mr3}
                                                         wrapperStyle={styles.sectionMenuItem}
                                                         focused={focused}
                                                         onPress={onPress}
diff --git a/src/pages/Search/SuggestedSearchSkeleton.tsx b/src/pages/Search/SuggestedSearchSkeleton.tsx
index e128504b3de4..5072b717a3eb 100644
--- a/src/pages/Search/SuggestedSearchSkeleton.tsx
+++ b/src/pages/Search/SuggestedSearchSkeleton.tsx
@@ -6,17 +6,43 @@ import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
 
 const SUGGESTED_SEARCH_SKELETON_TEST_ID = 'SuggestedSearchSkeleton';
 const NAV_ITEM_HEIGHT = 52;
+const SECTION_MENU_ITEM_HORIZONTAL_PADDING = 16;
+const SECTION_HEADER_HEIGHT = 32;
+const SECTION_HEADER_RECT_HEIGHT = 4;
+const ICON_LABEL_GAP = 12;
+const ICON_CONTAINER_SIZE = 20;
+const LOADING_ICON_SIZE = variables.iconSizeSmall;
+const ICON_CONTAINER_VERTICAL_OFFSET = (NAV_ITEM_HEIGHT - ICON_CONTAINER_SIZE) / 2;
+const ICON_INNER_OFFSET = (ICON_CONTAINER_SIZE - LOADING_ICON_SIZE) / 2;
+const ICON_VERTICAL_OFFSET = ICON_CONTAINER_VERTICAL_OFFSET + ICON_INNER_OFFSET;
+const LABEL_VERTICAL_OFFSET = 4;
 
 /** ---- Relative layout tokens ---- */
 const LHN = {
-    icon: {xVal: 18, yVal: 8, w: 16, h: 16, r: 4},
-    // label is positioned relative to icon
-    label: {dx: 30, dy: 4, w: 104, h: 8},
+    icon: {
+        xVal: SECTION_MENU_ITEM_HORIZONTAL_PADDING + ICON_INNER_OFFSET,
+        yVal: ICON_VERTICAL_OFFSET,
+        w: LOADING_ICON_SIZE,
+        h: LOADING_ICON_SIZE,
+        r: 4,
+    },
+    label: {
+        xVal: SECTION_MENU_ITEM_HORIZONTAL_PADDING + ICON_CONTAINER_SIZE + ICON_LABEL_GAP,
+        yVal: ICON_VERTICAL_OFFSET + LABEL_VERTICAL_OFFSET,
+        w: 104,
+        h: 8,
+    },
     // header bar in the small loader above each group
-    header: {xVal: 8, yVal: 0, w: 36, h: 4},
+    header: {
+        xVal: 8,
+        yVal: (SECTION_HEADER_HEIGHT - SECTION_HEADER_RECT_HEIGHT) / 2,
+        w: 36,
+        h: SECTION_HEADER_RECT_HEIGHT,
+    },
 };
 
 function SuggestedSearchSkeleton() {
@@ -26,8 +52,6 @@ function SuggestedSearchSkeleton() {
 
     const renderNavigationItem = () => {
         const {icon, label} = LHN;
-        const labelX = icon.xVal + label.dx;
-        const labelY = icon.yVal + label.dy;
 
         return (
             <>
@@ -41,7 +65,7 @@ function SuggestedSearchSkeleton() {
                 <Rect
                     width={label.w}
                     height={label.h}
-                    transform={[{translateX: labelX}, {translateY: labelY}]}
+                    transform={[{translateX: label.xVal}, {translateY: label.yVal}]}
                 />
             </>
         );
@@ -53,7 +77,7 @@ function SuggestedSearchSkeleton() {
         <>
             <SkeletonViewContentLoader
                 animate
-                height={24}
+                height={SECTION_HEADER_HEIGHT}
                 backgroundColor={theme.skeletonLHNIn}
                 foregroundColor={theme.skeletonLHNOut}
                 style={[styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]}
@@ -92,7 +116,7 @@ function SuggestedSearchSkeleton() {
     return (
         <View
             testID={SUGGESTED_SEARCH_SKELETON_TEST_ID}
-            style={[styles.flex1, styles.flexRow, styles.pb5, styles.pt5, styles.gap4, styles.pl4]}
+            style={[styles.flex1, styles.flexRow, styles.gap4]}
         >
             {navigationColumn}
         </View>

From d907a63890083bd0fe7286a1db244aedc21e9a64 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 17 Nov 2025 12:15:37 +0530
Subject: [PATCH 14/16] Improve design

---
 src/pages/Search/SearchTypeMenu.tsx | 30 +++++++++++------------------
 1 file changed, 11 insertions(+), 19 deletions(-)

diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index f19655f054cc..fe7d85bee133 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -1,7 +1,7 @@
 import {useIsFocused, useRoute} from '@react-navigation/native';
 import {accountIDSelector} from '@selectors/Session';
 import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef} from 'react';
-import {StyleSheet, View} from 'react-native';
+import {View} from 'react-native';
 // eslint-disable-next-line no-restricted-imports
 import type {ScrollView as RNScrollView, ScrollViewProps} from 'react-native';
 import Animated, {FadeIn} from 'react-native-reanimated';
@@ -197,23 +197,16 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
     }, [createSavedSearchMenuItem, savedSearches]);
 
     const renderSavedSearchesSection = useCallback(
-        (menuItems: MenuItemWithLink[]) => {
-            const menuItemsWithIconSpacing = menuItems.map((menuItem) => ({
-                ...menuItem,
-                iconStyles: [menuItem.iconStyles, styles.mr3],
-            }));
-
-            return (
-                <MenuItemList
-                    menuItems={menuItemsWithIconSpacing}
-                    wrapperStyle={styles.sectionMenuItem}
-                    icon={Expensicons.Bookmark}
-                    iconWidth={variables.iconSizeNormal}
-                    iconHeight={variables.iconSizeNormal}
-                    shouldUseSingleExecution
-                />
-            );
-        },
+        (menuItems: MenuItemWithLink[]) => (
+            <MenuItemList
+                menuItems={menuItems}
+                wrapperStyle={styles.sectionMenuItem}
+                icon={Expensicons.Bookmark}
+                iconWidth={variables.iconSizeNormal}
+                iconHeight={variables.iconSizeNormal}
+                shouldUseSingleExecution
+            />
+        ),
         [styles],
     );
 
@@ -280,7 +273,6 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
                                                         icon={item.icon}
                                                         iconWidth={variables.iconSizeNormal}
                                                         iconHeight={variables.iconSizeNormal}
-                                                        iconStyles={styles.mr3}
                                                         wrapperStyle={styles.sectionMenuItem}
                                                         focused={focused}
                                                         onPress={onPress}

From 9460f85853ddcea3656233a607b1c262b0a7f675 Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Mon, 17 Nov 2025 12:19:22 +0530
Subject: [PATCH 15/16] Lint fixes

---
 src/hooks/useSearchTypeMenu.tsx     | 1 +
 src/pages/Search/SearchTypeMenu.tsx | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/hooks/useSearchTypeMenu.tsx b/src/hooks/useSearchTypeMenu.tsx
index f6628c9badd0..0f96e3704c07 100644
--- a/src/hooks/useSearchTypeMenu.tsx
+++ b/src/hooks/useSearchTypeMenu.tsx
@@ -14,6 +14,7 @@ import {buildSearchQueryJSON, buildUserReadableQueryString} from '@libs/SearchQu
 import type {SavedSearchMenuItem} from '@libs/SearchUIUtils';
 import {createBaseSavedSearchMenuItem, getOverflowMenu as getOverflowMenuUtil} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
+// eslint-disable-next-line no-restricted-imports
 import * as Expensicons from '@src/components/Icon/Expensicons';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index fe7d85bee133..2ebe49fffe1c 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -30,6 +30,7 @@ import {buildSearchQueryJSON, buildUserReadableQueryString} from '@libs/SearchQu
 import type {SavedSearchMenuItem} from '@libs/SearchUIUtils';
 import {createBaseSavedSearchMenuItem, getOverflowMenu as getOverflowMenuUtil} from '@libs/SearchUIUtils';
 import variables from '@styles/variables';
+// eslint-disable-next-line no-restricted-imports
 import * as Expensicons from '@src/components/Icon/Expensicons';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';

From b1ee4a3d69104b1c5827add243679d93598c26ef Mon Sep 17 00:00:00 2001
From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com>
Date: Tue, 18 Nov 2025 19:29:08 +0530
Subject: [PATCH 16/16] Update to remove fade-in animation logic

---
 src/pages/Search/SearchTypeMenu.tsx | 49 +++++++++--------------------
 1 file changed, 14 insertions(+), 35 deletions(-)

diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 2ebe49fffe1c..69ea63487644 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -1,10 +1,9 @@
 import {useIsFocused, useRoute} from '@react-navigation/native';
 import {accountIDSelector} from '@selectors/Session';
-import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef} from 'react';
+import React, {useCallback, useContext, useLayoutEffect, useMemo, useRef} from 'react';
 import {View} from 'react-native';
 // eslint-disable-next-line no-restricted-imports
 import type {ScrollView as RNScrollView, ScrollViewProps} from 'react-native';
-import Animated, {FadeIn} from 'react-native-reanimated';
 import MenuItem from '@components/MenuItem';
 import type {MenuItemWithLink} from '@components/MenuItemList';
 import MenuItemList from '@components/MenuItemList';
@@ -71,19 +70,6 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
     const taxRates = getAllTaxRates();
     const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, {selector: accountIDSelector, canBeMissing: false});
     const {clearSelectedTransactions} = useSearchContext();
-    const initialSearchKeys = useRef<string[]>([]);
-
-    // The first time we render all of the sections the user can see, we need to mark these as 'rendered', such that we
-    // dont animate them in. We only animate in items that a user gains access to later on
-    useEffect(() => {
-        if (initialSearchKeys.current.length) {
-            return;
-        }
-
-        initialSearchKeys.current = typeMenuSections.flatMap((section) => {
-            return section.menuItems.map((item) => item.key);
-        });
-    }, [typeMenuSections]);
 
     const flattenedMenuItems = useMemo(() => typeMenuSections.flatMap((section) => section.menuItems), [typeMenuSections]);
 
@@ -259,27 +245,20 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
                                                 Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: item.searchQuery}));
                                             });
 
-                                            const isInitialItem = !initialSearchKeys.current.length || initialSearchKeys.current.includes(item.key);
-
                                             return (
-                                                <Animated.View
-                                                    key={item.translationPath}
-                                                    entering={!isInitialItem ? FadeIn : undefined}
-                                                >
-                                                    <MenuItem
-                                                        key={item.key}
-                                                        disabled={false}
-                                                        interactive
-                                                        title={translate(item.translationPath)}
-                                                        icon={item.icon}
-                                                        iconWidth={variables.iconSizeNormal}
-                                                        iconHeight={variables.iconSizeNormal}
-                                                        wrapperStyle={styles.sectionMenuItem}
-                                                        focused={focused}
-                                                        onPress={onPress}
-                                                        shouldIconUseAutoWidthStyle
-                                                    />
-                                                </Animated.View>
+                                                <MenuItem
+                                                    key={item.key}
+                                                    disabled={false}
+                                                    interactive
+                                                    title={translate(item.translationPath)}
+                                                    icon={item.icon}
+                                                    iconWidth={variables.iconSizeNormal}
+                                                    iconHeight={variables.iconSizeNormal}
+                                                    wrapperStyle={styles.sectionMenuItem}
+                                                    focused={focused}
+                                                    onPress={onPress}
+                                                    shouldIconUseAutoWidthStyle
+                                                />
                                             );
                                         })}
                                     </>
