From 6025f3611c8abd249dc7dbd2c82eb26b3159ece5 Mon Sep 17 00:00:00 2001
From: dmkt9 <demoskratos9x@gmail.com>
Date: Wed, 18 Jun 2025 08:35:48 +0700
Subject: [PATCH 1/5] Fix - Header link takes 2 steps back to expense report
 when reply is created in transaction thread

---
 src/libs/ReportUtils.ts       | 6 ++++--
 src/pages/home/HeaderView.tsx | 9 ++++++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index db07ccf634ff..7a8f89a38977 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -154,6 +154,7 @@ import {
     getReportActionHtml,
     getReportActionMessage as getReportActionMessageReportUtils,
     getReportActionMessageText,
+    getReportActions,
     getReportActionText,
     getRetractedMessage,
     getTravelUpdateMessage,
@@ -8631,9 +8632,9 @@ function navigateToLinkedReportAction(ancestor: Ancestor, isInNarrowPaneModal: b
     const parentReportAction = getReportAction(ancestor.report.parentReportID, ancestor.report.parentReportActionID);
 
     let newAncestor = ancestor;
-    // If `parentReport` is an IOU or Expense report, navigate directly to `parentReport`,
+    // If `parentReport` is an money report with one transaction, navigate directly to `parentReport`,
     // preventing redundant navigation when threading back to the parent chat thread
-    if (parentReport && parentReportAction && (isIOUReport(parentReport) || isExpenseReport(parentReport))) {
+    if (parentReport && parentReportAction && !!getOneTransactionThreadReportID(parentReport, getReportOrDraftReport(parentReport.chatReportID), getReportActions(parentReport))) {
         newAncestor = {
             ...ancestor,
             report: parentReport,
@@ -11252,6 +11253,7 @@ export {
     hasReportBeenReopened,
     getMoneyReportPreviewName,
     getNextApproverAccountID,
+    isOneTransactionReport,
 };
 
 export type {
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index b5a844dea00e..fbab0661c2de 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -34,6 +34,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import Navigation from '@libs/Navigation/Navigation';
 import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
 import Parser from '@libs/Parser';
+import {getOneTransactionThreadReportID, getReportActions} from '@libs/ReportActionsUtils';
 import {
     canJoinChat,
     canUserPerformWriteAction,
@@ -46,6 +47,7 @@ import {
     getPolicyName,
     getReportDescription,
     getReportName,
+    getReportOrDraftReport,
     hasReportNameError,
     isAdminRoom,
     isArchivedReport,
@@ -54,10 +56,8 @@ import {
     isChatUsedForOnboarding as isChatUsedForOnboardingReportUtils,
     isCurrentUserSubmitter,
     isDeprecatedGroupDM,
-    isExpenseReport,
     isExpenseRequest,
     isGroupChat as isGroupChatReportUtils,
-    isIOUReport,
     isOpenTaskReport,
     isPolicyExpenseChat as isPolicyExpenseChatReportUtils,
     isSelfDM as isSelfDMReportUtils,
@@ -140,7 +140,10 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked,
     const isTaskReport = isTaskReportReportUtils(report);
     const [parentOfParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReport?.parentReportID}`, {canBeMissing: true});
     const reportHeaderData =
-        ((!isTaskReport && !isChatThread) || (parentOfParentReport && (isIOUReport(parentOfParentReport) || isExpenseReport(parentOfParentReport)))) && report?.parentReportID
+        ((!isTaskReport && !isChatThread) ||
+            (parentOfParentReport &&
+                !!getOneTransactionThreadReportID(parentOfParentReport, getReportOrDraftReport(parentOfParentReport?.chatReportID), getReportActions(parentOfParentReport)))) &&
+        report?.parentReportID
             ? parentReport
             : report;
     // Use sorted display names for the title for group chats on native small screen widths

From cc92c846331aab8f7176aeb4c7e0bfa77977369c Mon Sep 17 00:00:00 2001
From: dmkt9 <demoskratos9x@gmail.com>
Date: Wed, 18 Jun 2025 09:43:00 +0700
Subject: [PATCH 2/5] update for filter visible report actions

---
 src/libs/ReportUtils.ts       | 18 ++++++++++++------
 src/pages/home/HeaderView.tsx | 14 ++++++++++++--
 2 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 7a8f89a38977..f7355648e960 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -8634,12 +8634,18 @@ function navigateToLinkedReportAction(ancestor: Ancestor, isInNarrowPaneModal: b
     let newAncestor = ancestor;
     // If `parentReport` is an money report with one transaction, navigate directly to `parentReport`,
     // preventing redundant navigation when threading back to the parent chat thread
-    if (parentReport && parentReportAction && !!getOneTransactionThreadReportID(parentReport, getReportOrDraftReport(parentReport.chatReportID), getReportActions(parentReport))) {
-        newAncestor = {
-            ...ancestor,
-            report: parentReport,
-            reportAction: parentReportAction,
-        };
+    if (parentReport && parentReportAction) {
+        const allParentReportActions = getReportActions(parentReport);
+        const visibleParentReportActions = Object.values(allParentReportActions ?? {}).filter(
+            (action) => !isMessageDeleted(action) || (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isOffline),
+        );
+        if (getOneTransactionThreadReportID(parentReport, getReportOrDraftReport(parentReport.chatReportID), visibleParentReportActions)) {
+            newAncestor = {
+                ...ancestor,
+                report: parentReport,
+                reportAction: parentReportAction,
+            };
+        }
     }
 
     if (isInNarrowPaneModal) {
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index fbab0661c2de..36aa4505ae90 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -25,6 +25,7 @@ import Text from '@components/Text';
 import Tooltip from '@components/Tooltip';
 import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing';
 import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
 import usePolicy from '@hooks/usePolicy';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
@@ -34,7 +35,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import Navigation from '@libs/Navigation/Navigation';
 import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
 import Parser from '@libs/Parser';
-import {getOneTransactionThreadReportID, getReportActions} from '@libs/ReportActionsUtils';
+import {getOneTransactionThreadReportID, getReportActions, isMessageDeleted} from '@libs/ReportActionsUtils';
 import {
     canJoinChat,
     canUserPerformWriteAction,
@@ -139,10 +140,19 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked,
     const isPolicyExpenseChat = isPolicyExpenseChatReportUtils(report);
     const isTaskReport = isTaskReportReportUtils(report);
     const [parentOfParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReport?.parentReportID}`, {canBeMissing: true});
+    const {isOffline} = useNetwork();
+    const visibleParentOfParentReportActions = useMemo(() => {
+        if (!parentOfParentReport) {
+            return;
+        }
+
+        const allReportActions = getReportActions(parentOfParentReport);
+        return Object.values(allReportActions ?? {}).filter((action) => !isMessageDeleted(action) || (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isOffline));
+    }, [parentOfParentReport, isOffline]);
     const reportHeaderData =
         ((!isTaskReport && !isChatThread) ||
             (parentOfParentReport &&
-                !!getOneTransactionThreadReportID(parentOfParentReport, getReportOrDraftReport(parentOfParentReport?.chatReportID), getReportActions(parentOfParentReport)))) &&
+                !!getOneTransactionThreadReportID(parentOfParentReport, getReportOrDraftReport(parentOfParentReport?.chatReportID), visibleParentOfParentReportActions))) &&
         report?.parentReportID
             ? parentReport
             : report;

From 245475da9f300ae441beb3e2931f733b3fd16608 Mon Sep 17 00:00:00 2001
From: dmkt9 <demoskratos9x@gmail.com>
Date: Wed, 18 Jun 2025 10:00:17 +0700
Subject: [PATCH 3/5] update

---
 src/libs/ReportUtils.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index f7355648e960..b204430b5286 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -11259,7 +11259,6 @@ export {
     hasReportBeenReopened,
     getMoneyReportPreviewName,
     getNextApproverAccountID,
-    isOneTransactionReport,
 };
 
 export type {

From e522b419f8b0c6fff92711c5e97c63c348bf8256 Mon Sep 17 00:00:00 2001
From: dmkt9 <demoskratos9x@gmail.com>
Date: Fri, 27 Jun 2025 12:55:11 +0700
Subject: [PATCH 4/5] refactor code

---
 src/libs/ReportActionsUtils.ts |  5 ++++-
 src/libs/ReportUtils.ts        | 22 ++++++++++------------
 src/pages/home/HeaderView.tsx  | 19 +++++++++----------
 3 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index b8a18ad084d4..0016fdec2f9f 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -1249,6 +1249,8 @@ const isIOUActionMatchingTransactionList = (
 /**
  * Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions.
  * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise.
+ *
+ * @param ignoreAllDeletedReportActions Filter out all deleted reportActions, even if they have visible childActions.
  */
 function getOneTransactionThreadReportID(
     report: OnyxEntry<Report>,
@@ -1256,6 +1258,7 @@ function getOneTransactionThreadReportID(
     reportActions: OnyxEntry<ReportActions> | ReportAction[],
     isOffline: boolean | undefined = undefined,
     reportTransactionIDs?: string[],
+    ignoreAllDeletedReportActions = false,
 ): string | undefined {
     // If the report is not an IOU, Expense report, or Invoice, it shouldn't be treated as one-transaction report.
     if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) {
@@ -1294,7 +1297,7 @@ function getOneTransactionThreadReportID(
             // - the action is pending deletion and the user is offline
             (!!originalMessage?.IOUTransactionID ||
                 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-                (isMessageDeleted(action) && action.childVisibleActionCount) ||
+                (ignoreAllDeletedReportActions ? !isMessageDeleted(action) : isMessageDeleted(action) && action.childVisibleActionCount) ||
                 (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && (isOffline ?? isNetworkOffline)))
         ) {
             iouRequestActions.push(action);
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index ce82849b11b7..384d7caca67d 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -8872,18 +8872,16 @@ function navigateToLinkedReportAction(ancestor: Ancestor, isInNarrowPaneModal: b
     let newAncestor = ancestor;
     // If `parentReport` is an money report with one transaction, navigate directly to `parentReport`,
     // preventing redundant navigation when threading back to the parent chat thread
-    if (parentReport && parentReportAction) {
-        const allParentReportActions = getReportActions(parentReport);
-        const visibleParentReportActions = Object.values(allParentReportActions ?? {}).filter(
-            (action) => !isMessageDeleted(action) || (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isOffline),
-        );
-        if (getOneTransactionThreadReportID(parentReport, getReportOrDraftReport(parentReport.chatReportID), visibleParentReportActions)) {
-            newAncestor = {
-                ...ancestor,
-                report: parentReport,
-                reportAction: parentReportAction,
-            };
-        }
+    if (
+        parentReport &&
+        parentReportAction &&
+        getOneTransactionThreadReportID(parentReport, getReportOrDraftReport(parentReport.chatReportID), getReportActions(parentReport), isOffline, undefined, true)
+    ) {
+        newAncestor = {
+            ...ancestor,
+            report: parentReport,
+            reportAction: parentReportAction,
+        };
     }
 
     if (isInNarrowPaneModal) {
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index b0f30ff79c53..dc3d376a64a7 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -36,7 +36,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import Navigation from '@libs/Navigation/Navigation';
 import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
 import Parser from '@libs/Parser';
-import {getOneTransactionThreadReportID, getReportActions, isMessageDeleted} from '@libs/ReportActionsUtils';
+import {getOneTransactionThreadReportID, getReportActions} from '@libs/ReportActionsUtils';
 import {
     canJoinChat,
     canUserPerformWriteAction,
@@ -142,18 +142,17 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked,
     const isTaskReport = isTaskReportReportUtils(report);
     const [parentOfParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReport?.parentReportID}`, {canBeMissing: true});
     const {isOffline} = useNetwork();
-    const visibleParentOfParentReportActions = useMemo(() => {
-        if (!parentOfParentReport) {
-            return;
-        }
-
-        const allReportActions = getReportActions(parentOfParentReport);
-        return Object.values(allReportActions ?? {}).filter((action) => !isMessageDeleted(action) || (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && isOffline));
-    }, [parentOfParentReport, isOffline]);
     const reportHeaderData =
         ((!isTaskReport && !isChatThread) ||
             (parentOfParentReport &&
-                !!getOneTransactionThreadReportID(parentOfParentReport, getReportOrDraftReport(parentOfParentReport?.chatReportID), visibleParentOfParentReportActions))) &&
+                !!getOneTransactionThreadReportID(
+                    parentOfParentReport,
+                    getReportOrDraftReport(parentOfParentReport?.chatReportID),
+                    getReportActions(parentOfParentReport),
+                    isOffline,
+                    undefined,
+                    true,
+                ))) &&
         report?.parentReportID
             ? parentReport
             : report;

From 187c7b3ff9a31965911b8b367aa254655cd84726 Mon Sep 17 00:00:00 2001
From: dmkt9 <demoskratos9x@gmail.com>
Date: Wed, 23 Jul 2025 23:50:06 +0700
Subject: [PATCH 5/5] updated to fix parent report return behavior

---
 src/libs/ReportUtils.ts       | 29 +++++------------------------
 src/pages/home/HeaderView.tsx | 20 +-------------------
 2 files changed, 6 insertions(+), 43 deletions(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index f497c0270a82..466566eece77 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -162,7 +162,6 @@ import {
     getReportActionHtml,
     getReportActionMessage as getReportActionMessageReportUtils,
     getReportActionMessageText,
-    getReportActions,
     getReportActionText,
     getRetractedMessage,
     getTravelUpdateMessage,
@@ -8808,29 +8807,11 @@ function isMoneyRequestReportPendingDeletion(reportOrID: OnyxEntry<Report> | str
 }
 
 function navigateToLinkedReportAction(ancestor: Ancestor, isInNarrowPaneModal: boolean, canUserPerformWrite: boolean | undefined, isOffline: boolean) {
-    const parentReport = getReportOrDraftReport(ancestor.report.parentReportID);
-    const parentReportAction = getReportAction(ancestor.report.parentReportID, ancestor.report.parentReportActionID);
-
-    let newAncestor = ancestor;
-    // If `parentReport` is an money report with one transaction, navigate directly to `parentReport`,
-    // preventing redundant navigation when threading back to the parent chat thread
-    if (
-        parentReport &&
-        parentReportAction &&
-        getOneTransactionThreadReportID(parentReport, getReportOrDraftReport(parentReport.chatReportID), getReportActions(parentReport), isOffline, undefined, true)
-    ) {
-        newAncestor = {
-            ...ancestor,
-            report: parentReport,
-            reportAction: parentReportAction,
-        };
-    }
-
     if (isInNarrowPaneModal) {
         Navigation.navigate(
             ROUTES.SEARCH_REPORT.getRoute({
-                reportID: newAncestor.report.reportID,
-                reportActionID: newAncestor.reportAction.reportActionID,
+                reportID: ancestor.report.reportID,
+                reportActionID: ancestor.reportAction.reportActionID,
                 backTo: SCREENS.SEARCH.REPORT_RHP,
             }),
         );
@@ -8838,13 +8819,13 @@ function navigateToLinkedReportAction(ancestor: Ancestor, isInNarrowPaneModal: b
     }
 
     // Pop the thread report screen before navigating to the chat report.
-    Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(newAncestor.report.reportID));
+    Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID));
 
-    const isVisibleAction = shouldReportActionBeVisible(newAncestor.reportAction, newAncestor.reportAction.reportActionID, canUserPerformWrite);
+    const isVisibleAction = shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID, canUserPerformWrite);
 
     if (isVisibleAction && !isOffline) {
         // Pop the chat report screen before navigating to the linked report action.
-        Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(newAncestor.report.reportID, newAncestor.reportAction.reportActionID));
+        Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID, ancestor.reportAction.reportActionID));
     }
 }
 
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index 36414aa19de0..d6779d8153dc 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -26,7 +26,6 @@ import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing';
 import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
-import useNetwork from '@hooks/useNetwork';
 import usePolicy from '@hooks/usePolicy';
 import useReportIsArchived from '@hooks/useReportIsArchived';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
@@ -37,7 +36,6 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import Navigation from '@libs/Navigation/Navigation';
 import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
 import Parser from '@libs/Parser';
-import {getOneTransactionThreadReportID, getReportActions} from '@libs/ReportActionsUtils';
 import {
     canJoinChat,
     canUserPerformWriteAction,
@@ -50,7 +48,6 @@ import {
     getPolicyName,
     getReportDescription,
     getReportName,
-    getReportOrDraftReport,
     hasReportNameError,
     isAdminRoom,
     isArchivedReport,
@@ -143,22 +140,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked,
     const isChatRoom = isChatRoomReportUtils(report);
     const isPolicyExpenseChat = isPolicyExpenseChatReportUtils(report);
     const isTaskReport = isTaskReportReportUtils(report);
-    const [parentOfParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReport?.parentReportID}`, {canBeMissing: true});
-    const {isOffline} = useNetwork();
-    const reportHeaderData =
-        ((!isTaskReport && !isChatThread) ||
-            (parentOfParentReport &&
-                !!getOneTransactionThreadReportID(
-                    parentOfParentReport,
-                    getReportOrDraftReport(parentOfParentReport?.chatReportID),
-                    getReportActions(parentOfParentReport),
-                    isOffline,
-                    undefined,
-                    true,
-                ))) &&
-        report?.parentReportID
-            ? parentReport
-            : report;
+    const reportHeaderData = !isTaskReport && !isChatThread && report?.parentReportID ? parentReport : report;
     // Use sorted display names for the title for group chats on native small screen widths
     const title = getReportName(reportHeaderData, policy, parentReportAction, personalDetails, invoiceReceiverPolicy);
     const subtitle = getChatRoomSubtitle(reportHeaderData);
