From 7ca73ea213c875363dab266e3d340104d71a8ecc Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Fri, 29 May 2026 03:54:52 +0530
Subject: [PATCH 01/11] Add Trip row in MoneyRequestView linking to trip room

---
 .../ReportActionItem/MoneyRequestView.tsx     | 19 +++++++++++++++++++
 src/types/onyx/Transaction.ts                 |  9 +++++++++
 2 files changed, 28 insertions(+)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 7d1ade75020a..d734753d9559 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -453,6 +453,10 @@ function MoneyRequestView({
     const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID);
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
+    const tripRoomReportID = transaction?.comment?.tripChatReportID;
+    const tripRoomName = transaction?.comment?.tripName;
+    const shouldShowTripRoomLink = !!tripRoomReportID && !!tripRoomName;
+
     const {getViolationsForField} = useViolations(transactionViolations ?? [], isTransactionScanning || !isPaidGroupPolicy(transactionThreadReport));
     const hasViolations = (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean =>
         getViolationsForField(field, data, policyHasDependentTags, tagValue).length > 0;
@@ -1331,6 +1335,21 @@ function MoneyRequestView({
                         />
                     </OfflineWithFeedback>
                 )}
+                {shouldShowTripRoomLink && (
+                    <>
+                        <MenuItemWithTopDescription
+                            title={tripRoomName}
+                            description={translate('travel.trip')}
+                            style={[styles.moneyRequestMenuItem]}
+                            titleStyle={[styles.flex1, styles.textBlue]}
+                            onPress={() => {
+                                Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID));
+                            }}
+                            interactive
+                        />
+                        <View style={styles.reportHorizontalRule} />
+                    </>
+                )}
                 {/* Note: "View trip details" should be always the last item */}
                 {shouldShowViewTripDetails && (
                     <MenuItem
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index 23111a9b89b4..a77ecd1ca278 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -152,6 +152,15 @@ type Comment = {
 
     /** Odometer end image (File object with uri on web, URI string on native) */
     odometerEndImage?: FileObject | string;
+
+    /** Spotnana trip ID, set on travel transactions and used to link the expense to its trip room */
+    tripID?: string;
+
+    /** Trip room report ID, set on travel transactions so the expense can deep-link directly to its trip room */
+    tripChatReportID?: string;
+
+    /** Trip room display name, set on travel transactions for rendering the trip room link label */
+    tripName?: string;
 };
 
 /** Model of transaction custom unit */

From b9957dd1e7ba7ea14e05fa40aed5134a651cee0f Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Fri, 29 May 2026 21:13:41 +0530
Subject: [PATCH 02/11] Preserve active route as backTo when opening trip room

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index b6cc7b04a86f..076e8702b5c5 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -1352,9 +1352,9 @@ function MoneyRequestView({
                             description={translate('travel.trip')}
                             style={[styles.moneyRequestMenuItem]}
                             titleStyle={[styles.flex1, styles.textBlue]}
-                            onPress={() => {
-                                Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID));
-                            }}
+                        onPress={() => {
+                            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID, undefined, undefined, Navigation.getActiveRoute()));
+                        }}
                             interactive
                         />
                         <View style={styles.reportHorizontalRule} />

From 429a4da7bbc1cf94de3f045e7816e48f994be9df Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Sat, 30 May 2026 00:22:08 +0530
Subject: [PATCH 03/11] Resolve trip room via reports collection lookup instead
 of denormalized NVPs

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 12 ++++++++++--
 src/types/onyx/Transaction.ts                        |  6 ------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 076e8702b5c5..8e8385d10b06 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -455,8 +455,16 @@ function MoneyRequestView({
     const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID);
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
-    const tripRoomReportID = transaction?.comment?.tripChatReportID;
-    const tripRoomName = transaction?.comment?.tripName;
+    const transactionTripID = transaction?.comment?.tripID;
+    const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
+        if (!transactionTripID || !reports) {
+            return undefined;
+        }
+        return Object.values(reports).find((candidateReport) => candidateReport?.tripData?.tripID === transactionTripID);
+    };
+    const [tripRoomReport] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID]);
+    const tripRoomReportID = tripRoomReport?.reportID;
+    const tripRoomName = tripRoomReport ? getReportName(tripRoomReport, reportAttributes) || tripRoomReport.reportName : undefined;
     const shouldShowTripRoomLink = !!tripRoomReportID && !!tripRoomName;
 
     const {getViolationsForField} = useViolations(transactionViolations ?? [], isTransactionScanning || !isPaidGroupPolicy(transactionThreadReport));
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index a77ecd1ca278..2d5023afe7ba 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -155,12 +155,6 @@ type Comment = {
 
     /** Spotnana trip ID, set on travel transactions and used to link the expense to its trip room */
     tripID?: string;
-
-    /** Trip room report ID, set on travel transactions so the expense can deep-link directly to its trip room */
-    tripChatReportID?: string;
-
-    /** Trip room display name, set on travel transactions for rendering the trip room link label */
-    tripName?: string;
 };
 
 /** Model of transaction custom unit */

From c43954e38ef9c3b4f234e7db5b996c7910cae09d Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Sat, 30 May 2026 02:31:09 +0530
Subject: [PATCH 04/11] Try O(1) grandparent lookup before O(n) reports scan,
 and add tripID to DebugUtils validators

---
 .../ReportActionItem/MoneyRequestView.tsx          | 14 ++++++++++----
 src/libs/DebugUtils.ts                             |  2 ++
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 8e8385d10b06..65495bdefee5 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -456,13 +456,19 @@ function MoneyRequestView({
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
     const transactionTripID = transaction?.comment?.tripID;
+    // Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
+    const grandparentReportID = parentReport?.parentReportID;
     const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
         if (!transactionTripID || !reports) {
             return undefined;
         }
+        const grandparent = grandparentReportID ? reports[`${ONYXKEYS.COLLECTION.REPORT}${grandparentReportID}`] : undefined;
+        if (grandparent?.tripData?.tripID === transactionTripID) {
+            return grandparent;
+        }
         return Object.values(reports).find((candidateReport) => candidateReport?.tripData?.tripID === transactionTripID);
     };
-    const [tripRoomReport] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID]);
+    const [tripRoomReport] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID, grandparentReportID]);
     const tripRoomReportID = tripRoomReport?.reportID;
     const tripRoomName = tripRoomReport ? getReportName(tripRoomReport, reportAttributes) || tripRoomReport.reportName : undefined;
     const shouldShowTripRoomLink = !!tripRoomReportID && !!tripRoomName;
@@ -1360,9 +1366,9 @@ function MoneyRequestView({
                             description={translate('travel.trip')}
                             style={[styles.moneyRequestMenuItem]}
                             titleStyle={[styles.flex1, styles.textBlue]}
-                        onPress={() => {
-                            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID, undefined, undefined, Navigation.getActiveRoute()));
-                        }}
+                            onPress={() => {
+                                Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID, undefined, undefined, Navigation.getActiveRoute()));
+                            }}
                             interactive
                         />
                         <View style={styles.reportHorizontalRule} />
diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts
index e1823b267035..926dbc758cef 100644
--- a/src/libs/DebugUtils.ts
+++ b/src/libs/DebugUtils.ts
@@ -1072,6 +1072,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
                     odometerEnd: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     odometerStartImage: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     odometerEndImage: CONST.RED_BRICK_ROAD_PENDING_ACTION,
+                    tripID: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     attendees: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     amount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     taxAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
@@ -1194,6 +1195,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
                 odometerEnd: 'number',
                 odometerStartImage: 'object',
                 odometerEndImage: 'object',
+                tripID: 'string',
             });
         case 'accountant':
             return validateObject<ObjectElement<Transaction, 'accountant'>>(value, {

From da554d256bd2e48712251db88107464842845f34 Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Sat, 30 May 2026 02:39:39 +0530
Subject: [PATCH 05/11] fix style

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 65495bdefee5..d98b309306a9 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -456,6 +456,7 @@ function MoneyRequestView({
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
     const transactionTripID = transaction?.comment?.tripID;
+
     // Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
     const grandparentReportID = parentReport?.parentReportID;
     const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {

From dc97a972f70f60b24a54c8461c0d150242fc59b7 Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Sat, 30 May 2026 02:49:23 +0530
Subject: [PATCH 06/11] Narrow trip room selector to return only reportID and
 name so Onyx deepEqual stays cheap

---
 .../ReportActionItem/MoneyRequestView.tsx     | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index d98b309306a9..60f59cb23737 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -458,20 +458,27 @@ function MoneyRequestView({
     const transactionTripID = transaction?.comment?.tripID;
 
     // Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
+    // Narrow the selector output to just the two primitives we render so Onyx's deepEqual stays cheap
+    // on every report collection mutation (PERF-11).
     const grandparentReportID = parentReport?.parentReportID;
     const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
         if (!transactionTripID || !reports) {
             return undefined;
         }
         const grandparent = grandparentReportID ? reports[`${ONYXKEYS.COLLECTION.REPORT}${grandparentReportID}`] : undefined;
-        if (grandparent?.tripData?.tripID === transactionTripID) {
-            return grandparent;
+        const match =
+            grandparent?.tripData?.tripID === transactionTripID ? grandparent : Object.values(reports).find((candidateReport) => candidateReport?.tripData?.tripID === transactionTripID);
+        if (!match?.reportID) {
+            return undefined;
         }
-        return Object.values(reports).find((candidateReport) => candidateReport?.tripData?.tripID === transactionTripID);
+        return {
+            reportID: match.reportID,
+            name: getReportName(match, reportAttributes) || match.reportName,
+        };
     };
-    const [tripRoomReport] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID, grandparentReportID]);
-    const tripRoomReportID = tripRoomReport?.reportID;
-    const tripRoomName = tripRoomReport ? getReportName(tripRoomReport, reportAttributes) || tripRoomReport.reportName : undefined;
+    const [tripRoomInfo] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID, grandparentReportID, reportAttributes]);
+    const tripRoomReportID = tripRoomInfo?.reportID;
+    const tripRoomName = tripRoomInfo?.name;
     const shouldShowTripRoomLink = !!tripRoomReportID && !!tripRoomName;
 
     const {getViolationsForField} = useViolations(transactionViolations ?? [], isTransactionScanning || !isPaidGroupPolicy(transactionThreadReport));

From 7b4fe96e318cdff8c0d8c8eb5e740a2f9a8f71a1 Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Sat, 30 May 2026 02:53:41 +0530
Subject: [PATCH 07/11] remove bad comment

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 60f59cb23737..6541494c962b 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -458,8 +458,6 @@ function MoneyRequestView({
     const transactionTripID = transaction?.comment?.tripID;
 
     // Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
-    // Narrow the selector output to just the two primitives we render so Onyx's deepEqual stays cheap
-    // on every report collection mutation (PERF-11).
     const grandparentReportID = parentReport?.parentReportID;
     const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
         if (!transactionTripID || !reports) {

From 9765c4576f038cd75c8092b0e334a0b5d1295db5 Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Sat, 30 May 2026 03:05:27 +0530
Subject: [PATCH 08/11] Use originalUseOnyx so trip room lookup hits live data
 instead of Search snapshot

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 6541494c962b..7d7cdf2a20c6 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -2,6 +2,11 @@ import {Str} from 'expensify-common';
 import React, {useState} from 'react';
 import {View} from 'react-native';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+// Use the original useOnyx hook to scan the live report collection when resolving the trip room.
+// @hooks/useOnyx redirects collection reads to the search snapshot under Search routes, which
+// typically does not include the grandparent trip room and would leave the Trip row hidden.
+// eslint-disable-next-line no-restricted-imports
+import {useOnyx as originalUseOnyx} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import DotIndicatorMessage from '@components/DotIndicatorMessage';
 import Icon from '@components/Icon';
@@ -458,6 +463,8 @@ function MoneyRequestView({
     const transactionTripID = transaction?.comment?.tripID;
 
     // Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
+    // Use originalUseOnyx so the lookup hits the live report collection - the grandparent trip room
+    // is typically not in the Search snapshot.
     const grandparentReportID = parentReport?.parentReportID;
     const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
         if (!transactionTripID || !reports) {
@@ -474,7 +481,7 @@ function MoneyRequestView({
             name: getReportName(match, reportAttributes) || match.reportName,
         };
     };
-    const [tripRoomInfo] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID, grandparentReportID, reportAttributes]);
+    const [tripRoomInfo] = originalUseOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID, grandparentReportID, reportAttributes]);
     const tripRoomReportID = tripRoomInfo?.reportID;
     const tripRoomName = tripRoomInfo?.name;
     const shouldShowTripRoomLink = !!tripRoomReportID && !!tripRoomName;

From b708c2d83f3cb55fe1cb2d0fd5779d8bb194b168 Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Mon, 1 Jun 2026 19:40:09 +0530
Subject: [PATCH 09/11] Re-trigger CI (flaky MiddlewareTest/UnreadIndicators)


From 8d18ec21deb74023ad65476148c7932506dc5964 Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Tue, 2 Jun 2026 03:20:32 +0530
Subject: [PATCH 10/11] fix comments

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index a4043f0be45d..df35b614560e 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -2,9 +2,7 @@ import {Str} from 'expensify-common';
 import React, {useState} from 'react';
 import {View} from 'react-native';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
-// Use the original useOnyx hook to scan the live report collection when resolving the trip room.
-// @hooks/useOnyx redirects collection reads to the search snapshot under Search routes, which
-// typically does not include the grandparent trip room and would leave the Trip row hidden.
+// Use the original useOnyx hook to get the real-time data from Onyx and not from the snapshot
 // eslint-disable-next-line no-restricted-imports
 import {useOnyx as originalUseOnyx} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
@@ -463,9 +461,7 @@ function MoneyRequestView({
 
     const transactionTripID = transaction?.comment?.tripID;
 
-    // Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
-    // Use originalUseOnyx so the lookup hits the live report collection - the grandparent trip room
-    // is typically not in the Search snapshot.
+    // Trip rooms are the grandparent report, so check that first before scanning the collection.
     const grandparentReportID = parentReport?.parentReportID;
     const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
         if (!transactionTripID || !reports) {

From d8d35a968dce678d6e43e1ca348486571822bc5c Mon Sep 17 00:00:00 2001
From: Ishpaul Singh <ishpaul207@gmail.com>
Date: Wed, 3 Jun 2026 19:32:56 +0530
Subject: [PATCH 11/11] Style Trip row as push row with two-line title wrap.

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index df35b614560e..d3f3e8ee7ab6 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -1376,7 +1376,9 @@ function MoneyRequestView({
                             title={tripRoomName}
                             description={translate('travel.trip')}
                             style={[styles.moneyRequestMenuItem]}
-                            titleStyle={[styles.flex1, styles.textBlue]}
+                            titleStyle={styles.flex1}
+                            numberOfLinesTitle={2}
+                            shouldShowRightIcon
                             onPress={() => {
                                 Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID, undefined, undefined, Navigation.getActiveRoute()));
                             }}
