From b3f37f25204b7d2b1cf1633e0321fd695db38c78 Mon Sep 17 00:00:00 2001
From: Wojciech Boman <wojciechboman@gmail.com>
Date: Thu, 14 May 2026 16:10:07 +0200
Subject: [PATCH 1/2] Navigate to report thread in RHP when viewing a report in
 a right docked modal

---
 .../MoneyRequestReportNavigation.tsx          |  2 +
 src/components/ParentNavigationSubtitle.tsx   | 20 ++--
 .../Navigators/RightModalNavigator.tsx        |  4 +-
 .../helpers/getTopmostFullScreenRoute.ts      | 24 +++++
 .../helpers/isReportTopmostSplitNavigator.ts  | 14 +--
 .../helpers/isSearchTopmostFullScreenRoute.ts | 14 +--
 src/libs/ReportUtils.ts                       | 13 +++
 src/libs/actions/Report/index.ts              | 13 ++-
 src/pages/inbox/HeaderView.tsx                |  2 +
 .../ComposerWithSuggestions.tsx               |  6 +-
 .../getTopmostFullScreenRouteTest.ts          | 93 +++++++++++++++++++
 11 files changed, 171 insertions(+), 34 deletions(-)
 create mode 100644 src/libs/Navigation/helpers/getTopmostFullScreenRoute.ts
 create mode 100644 tests/unit/Navigation/getTopmostFullScreenRouteTest.ts

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
index f3739c106841..3cf1971a1631 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportNavigation.tsx
@@ -147,6 +147,8 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
         });
         Navigation.setParams({
             reportID: reportId,
+            reportActionID: undefined,
+            referrer: undefined,
         });
     };
 
diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx
index 085044839cbe..850d65b2f582 100644
--- a/src/components/ParentNavigationSubtitle.tsx
+++ b/src/components/ParentNavigationSubtitle.tsx
@@ -186,14 +186,12 @@ function ParentNavigationSubtitle({
             // avoid stacking RHPs by going back to the search report if it's already there
             const previousRoute = currentFocusedNavigator?.state?.routes.at(-2);
 
-            if (previousRoute?.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT && lastRoute?.name === SCREENS.RIGHT_MODAL.EXPENSE_REPORT) {
-                if (previousRoute.params && 'reportID' in previousRoute.params) {
-                    const reportIDFromParams = previousRoute.params.reportID;
+            if (previousRoute?.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT && previousRoute.params && 'reportID' in previousRoute.params) {
+                const reportIDFromParams = previousRoute.params.reportID;
 
-                    if (reportIDFromParams === parentReportID) {
-                        Navigation.goBack();
-                        return;
-                    }
+                if (reportIDFromParams === parentReportID) {
+                    Navigation.goBack();
+                    return;
                 }
             }
 
@@ -213,6 +211,14 @@ function ParentNavigationSubtitle({
                     return;
                 }
             }
+
+            // Stay in the Search tab when the parent link is tapped from a SEARCH_REPORT RHP
+            // and the parent isn't already in the stack — otherwise the REPORT_WITH_ID fallback
+            // would yank the user to Inbox.
+            if (isReportInRHP) {
+                Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: parentReportID, reportActionID: isVisibleAction ? parentReportActionID : undefined}));
+                return;
+            }
         }
 
         // If the parent report is already the previous screen in the main stack, go back to it
diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
index 81851f3303f4..26c80e3e4710 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
@@ -105,7 +105,7 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) {
     const containerRef = useRef(null);
     const isExecutingRef = useRef<boolean>(false);
     const screenOptions = useRHPScreenOptions();
-    const {superWideRHPRouteKeys, shouldRenderTertiaryOverlay} = useWideRHPState();
+    const {superWideRHPRouteKeys, wideRHPRouteKeys, shouldRenderTertiaryOverlay} = useWideRHPState();
     const {clearWideRHPKeys, syncRHPKeys} = useWideRHPActions();
     const {windowWidth} = useWindowDimensions();
     const modalStackScreenOptions = useModalStackScreenOptions();
@@ -130,7 +130,7 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) {
 
     // Animation should be disabled when we open the wide rhp from the narrow one.
     // When the wide rhp page is opened as first one, it will be animated with the entire RightModalNavigator.
-    const animationEnabledOnSearchReport = superWideRHPRouteKeys.length > 0 || isSmallScreenWidth;
+    const animationEnabledOnSearchReport = superWideRHPRouteKeys.length > 0 || wideRHPRouteKeys.length > 0 || isSmallScreenWidth;
 
     const animatedWidth = expandedRHPProgress.interpolate({
         inputRange: [0, 1, 2],
diff --git a/src/libs/Navigation/helpers/getTopmostFullScreenRoute.ts b/src/libs/Navigation/helpers/getTopmostFullScreenRoute.ts
new file mode 100644
index 000000000000..9162613745f0
--- /dev/null
+++ b/src/libs/Navigation/helpers/getTopmostFullScreenRoute.ts
@@ -0,0 +1,24 @@
+import {navigationRef} from '@libs/Navigation/Navigation';
+import type {NavigationRoute, RootNavigatorParamList, State} from '@libs/Navigation/types';
+import NAVIGATORS from '@src/NAVIGATORS';
+
+/**
+ * Returns the active tab route of the topmost TAB_NAVIGATOR in the root navigation state.
+ * Use this to determine which full-screen tab (Search, Inbox, etc.) is currently focused.
+ */
+function getTopmostFullScreenRoute(): NavigationRoute | undefined {
+    const rootState = navigationRef.getRootState() as State<RootNavigatorParamList>;
+
+    if (!rootState) {
+        return undefined;
+    }
+
+    const topmostTabNavigatorRoute = rootState.routes.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR);
+    if (!topmostTabNavigatorRoute?.state) {
+        return undefined;
+    }
+    const index = topmostTabNavigatorRoute.state.index ?? 0;
+    return topmostTabNavigatorRoute.state.routes?.at(index);
+}
+
+export default getTopmostFullScreenRoute;
diff --git a/src/libs/Navigation/helpers/isReportTopmostSplitNavigator.ts b/src/libs/Navigation/helpers/isReportTopmostSplitNavigator.ts
index 4b840387f45a..c9b7c33bf599 100644
--- a/src/libs/Navigation/helpers/isReportTopmostSplitNavigator.ts
+++ b/src/libs/Navigation/helpers/isReportTopmostSplitNavigator.ts
@@ -1,18 +1,12 @@
-import {navigationRef} from '@libs/Navigation/Navigation';
-import type {RootNavigatorParamList, State} from '@libs/Navigation/types';
 import NAVIGATORS from '@src/NAVIGATORS';
-import getActiveTabName from './getActiveTabName';
-import {isFullScreenName} from './isNavigatorName';
+import getTopmostFullScreenRoute from './getTopmostFullScreenRoute';
 
 const isReportTopmostSplitNavigator = (): boolean => {
-    const rootState = navigationRef.getRootState() as State<RootNavigatorParamList>;
-
-    if (!rootState) {
+    const topmostFullScreenRoute = getTopmostFullScreenRoute();
+    if (!topmostFullScreenRoute) {
         return false;
     }
-
-    const topmostFullScreenRoute = rootState.routes.findLast((route) => isFullScreenName(route.name));
-    return getActiveTabName(topmostFullScreenRoute) === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR;
+    return topmostFullScreenRoute.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR;
 };
 
 export default isReportTopmostSplitNavigator;
diff --git a/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts b/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts
index 00631ae00e5e..9fc3b6a725f7 100644
--- a/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts
+++ b/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts
@@ -1,18 +1,12 @@
-import {navigationRef} from '@libs/Navigation/Navigation';
-import type {RootNavigatorParamList, State} from '@libs/Navigation/types';
 import NAVIGATORS from '@src/NAVIGATORS';
-import getActiveTabName from './getActiveTabName';
-import {isFullScreenName} from './isNavigatorName';
+import getTopmostFullScreenRoute from './getTopmostFullScreenRoute';
 
 const isSearchTopmostFullScreenRoute = (): boolean => {
-    const rootState = navigationRef.getRootState() as State<RootNavigatorParamList>;
-
-    if (!rootState) {
+    const topmostFullScreenRoute = getTopmostFullScreenRoute();
+    if (!topmostFullScreenRoute) {
         return false;
     }
-
-    const topmostFullScreenRoute = rootState.routes.findLast((route) => isFullScreenName(route.name));
-    return getActiveTabName(topmostFullScreenRoute) === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR;
+    return topmostFullScreenRoute.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR;
 };
 
 export default isSearchTopmostFullScreenRoute;
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index d81bfb67e636..86a5a4e6da88 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -10248,6 +10248,19 @@ function navigateToLinkedReportAction(
     visibleReportActionsData?: VisibleReportActionsDerivedValue,
 ) {
     if (isInNarrowPaneModal) {
+        const rootState = navigationRef.current?.getRootState();
+        const topmostRHP = rootState?.routes.findLast((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR);
+        const rhpState = topmostRHP?.state;
+        const rhpIndex = rhpState?.index ?? (rhpState?.routes.length ? rhpState.routes.length - 1 : -1);
+        const routeBelow = rhpIndex > 0 ? rhpState?.routes.at(rhpIndex - 1) : undefined;
+        const routeBelowParams = routeBelow?.params as {reportID?: string} | undefined;
+
+        if (routeBelow?.key && routeBelowParams?.reportID === ancestor.report.reportID) {
+            Navigation.setParams({reportActionID: ancestor.reportAction.reportActionID}, routeBelow.key, rhpState?.key);
+            Navigation.goBack();
+            return;
+        }
+
         Navigation.navigate(
             ROUTES.SEARCH_REPORT.getRoute({
                 reportID: ancestor.report.reportID,
diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts
index d667f59fd4c1..1b91d15105a8 100644
--- a/src/libs/actions/Report/index.ts
+++ b/src/libs/actions/Report/index.ts
@@ -75,6 +75,7 @@ import Log from '@libs/Log';
 import {isEmailPublicDomain} from '@libs/LoginUtils';
 import {getMovedReportID} from '@libs/ModifiedExpenseMessage';
 import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
+import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute';
 import type {LinkToOptions} from '@libs/Navigation/helpers/linkTo/types';
 import Navigation from '@libs/Navigation/Navigation';
 import enhanceParameters from '@libs/Network/enhanceParameters';
@@ -2440,7 +2441,11 @@ function navigateToAndOpenChildReport(
 ) {
     const report = childReport ?? createChildReport(childReport, parentReportAction, parentReport, currentUserAccountID, introSelected, betas, isSelfTourViewed, personalDetails);
 
-    Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID, undefined, undefined, Navigation.getActiveRoute()));
+    if (isSearchTopmostFullScreenRoute()) {
+        Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: report.reportID, backTo: Navigation.getActiveRoute()}));
+    } else {
+        Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID, undefined, undefined, Navigation.getActiveRoute()));
+    }
 }
 
 /**
@@ -2533,7 +2538,11 @@ function explain(
     // Check if explanation thread report already exists
     const report = childReport ?? createChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, isSelfTourViewed);
 
-    Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID, undefined, undefined, Navigation.getActiveRoute()));
+    if (isSearchTopmostFullScreenRoute()) {
+        Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: report.reportID, backTo: Navigation.getActiveRoute()}));
+    } else {
+        Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID, undefined, undefined, Navigation.getActiveRoute()));
+    }
     // Schedule adding the explanation comment on the next animation frame
     // so it runs immediately after navigation completes.
     requestAnimationFrame(() => {
diff --git a/src/pages/inbox/HeaderView.tsx b/src/pages/inbox/HeaderView.tsx
index 0a7476f7db9d..85e2ff34ba89 100644
--- a/src/pages/inbox/HeaderView.tsx
+++ b/src/pages/inbox/HeaderView.tsx
@@ -105,6 +105,7 @@ function HeaderView({onNavigationMenuButtonClicked, reportID}: HeaderViewProps)
     const {isSmallScreenWidth, shouldUseNarrowLayout, isInLandscapeMode} = useResponsiveLayout();
     const isInSidePanel = useIsInSidePanel();
     const route = useRoute();
+    const openParentReportInCurrentTab = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT;
     const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID) ?? getNonEmptyStringOnyxID(report?.reportID)}`);
     const [grandParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(parentReport?.parentReportID)}`);
     const grandParentReportAction = useParentReportAction(parentReport);
@@ -346,6 +347,7 @@ function HeaderView({onNavigationMenuButtonClicked, reportID}: HeaderViewProps)
                                                 parentReportID={parentNavigationReport?.parentReportID}
                                                 parentReportActionID={isParentOneTransactionThread ? undefined : parentNavigationReport?.parentReportActionID}
                                                 pressableStyles={[styles.alignSelfStart, styles.mw100]}
+                                                openParentReportInCurrentTab={openParentReportInCurrentTab}
                                                 humanAgentAccountID={humanAgentAccountID}
                                                 humanAgentName={humanAgentName}
                                             />
diff --git a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx
index 8dcfebdd6049..432ac45da15e 100644
--- a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx
+++ b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx
@@ -310,9 +310,9 @@ function ComposerWithSuggestions({
 
     const commentRef = useRef(initialText);
 
-    const {superWideRHPRouteKeys} = useWideRHPState();
-    // When SearchReport is stacked above another RHP, delay autofocus until after the transition completes to avoid animation jank
-    const shouldDelayAutoFocus = superWideRHPRouteKeys.length > 0 && route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT;
+    const {superWideRHPRouteKeys, wideRHPRouteKeys} = useWideRHPState();
+    // When SearchReport is stacked above another RHP (wide or super-wide), delay autofocus until after the transition completes to avoid animation jank
+    const shouldDelayAutoFocus = (superWideRHPRouteKeys.length > 0 || wideRHPRouteKeys.length > 0) && route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT;
     const shouldDelayAutoFocusRef = useRef(shouldDelayAutoFocus);
     shouldDelayAutoFocusRef.current = shouldDelayAutoFocus;
 
diff --git a/tests/unit/Navigation/getTopmostFullScreenRouteTest.ts b/tests/unit/Navigation/getTopmostFullScreenRouteTest.ts
new file mode 100644
index 000000000000..6dc2760abe33
--- /dev/null
+++ b/tests/unit/Navigation/getTopmostFullScreenRouteTest.ts
@@ -0,0 +1,93 @@
+import getTopmostFullScreenRoute from '@libs/Navigation/helpers/getTopmostFullScreenRoute';
+import NAVIGATORS from '@src/NAVIGATORS';
+
+const mockGetRootState = jest.fn();
+
+jest.mock('@libs/Navigation/Navigation', () => ({
+    navigationRef: {
+        getRootState: () => mockGetRootState() as unknown,
+    },
+}));
+
+describe('getTopmostFullScreenRoute', () => {
+    beforeEach(() => {
+        mockGetRootState.mockReset();
+    });
+
+    it('returns undefined when there is no root state', () => {
+        mockGetRootState.mockReturnValue(undefined);
+        expect(getTopmostFullScreenRoute()).toBeUndefined();
+    });
+
+    it('returns undefined when there is no TAB_NAVIGATOR route in the root state', () => {
+        mockGetRootState.mockReturnValue({
+            routes: [{name: NAVIGATORS.RIGHT_MODAL_NAVIGATOR}, {name: NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR}],
+        });
+        expect(getTopmostFullScreenRoute()).toBeUndefined();
+    });
+
+    it('returns undefined when the TAB_NAVIGATOR has no nested state yet', () => {
+        mockGetRootState.mockReturnValue({
+            routes: [{name: NAVIGATORS.TAB_NAVIGATOR}],
+        });
+        expect(getTopmostFullScreenRoute()).toBeUndefined();
+    });
+
+    it('returns the focused tab route based on state.index', () => {
+        const reportsRoute = {name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR};
+        const searchRoute = {name: NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR};
+        mockGetRootState.mockReturnValue({
+            routes: [
+                {
+                    name: NAVIGATORS.TAB_NAVIGATOR,
+                    state: {
+                        index: 1,
+                        routes: [reportsRoute, searchRoute],
+                    },
+                },
+            ],
+        });
+        expect(getTopmostFullScreenRoute()).toBe(searchRoute);
+    });
+
+    it('falls back to the first child route when state.index is missing', () => {
+        const reportsRoute = {name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR};
+        const searchRoute = {name: NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR};
+        mockGetRootState.mockReturnValue({
+            routes: [
+                {
+                    name: NAVIGATORS.TAB_NAVIGATOR,
+                    state: {
+                        routes: [reportsRoute, searchRoute],
+                    },
+                },
+            ],
+        });
+        expect(getTopmostFullScreenRoute()).toBe(reportsRoute);
+    });
+
+    it('returns the focused tab of the topmost TAB_NAVIGATOR when multiple exist', () => {
+        const oldFocused = {name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR};
+        const newFocused = {name: NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR};
+        mockGetRootState.mockReturnValue({
+            routes: [
+                {
+                    name: NAVIGATORS.TAB_NAVIGATOR,
+                    state: {
+                        index: 0,
+                        routes: [oldFocused],
+                    },
+                },
+                {name: NAVIGATORS.RIGHT_MODAL_NAVIGATOR},
+                {
+                    name: NAVIGATORS.TAB_NAVIGATOR,
+                    state: {
+                        index: 0,
+                        routes: [newFocused],
+                    },
+                },
+            ],
+        });
+        expect(getTopmostFullScreenRoute()).toBe(newFocused);
+    });
+});

From 31e9d03e42685389bdd2ad7757f181eb17abba7d Mon Sep 17 00:00:00 2001
From: Wojciech Boman <wojciechboman@gmail.com>
Date: Fri, 22 May 2026 15:38:31 +0200
Subject: [PATCH 2/2] Clear reportActionID when navigating between transaction
 threads

---
 .../MoneyRequestReportTransactionsNavigation.tsx              | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx
index 68952caf8a0b..b4c41f0352ed 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx
@@ -125,7 +125,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
             backTo = params?.backTo ?? backTo;
         }
         const nextThreadReportID = nextParentReportAction?.childReportID;
-        const navigationParams = {reportID: nextThreadReportID, backTo};
+        const navigationParams = {reportID: nextThreadReportID, reportActionID: undefined, backTo};
 
         if (nextThreadReportID) {
             markReportIDAsExpense(nextThreadReportID);
@@ -161,7 +161,7 @@ function MoneyRequestReportTransactionsNavigation({currentTransactionID, isFromR
             backTo = params?.backTo ?? backTo;
         }
         const prevThreadReportID = prevParentReportAction?.childReportID;
-        const navigationParams = {reportID: prevThreadReportID, backTo};
+        const navigationParams = {reportID: prevThreadReportID, reportActionID: undefined, backTo};
 
         if (prevThreadReportID) {
             markReportIDAsExpense(prevThreadReportID);
