From ce0b3268b834af6dd3e0b1857a3b9e462d6236ad Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 13 Apr 2026 22:20:29 +0430 Subject: [PATCH 01/18] fix: correct Audit Reports navigation and prioritize report routes --- .../ReportActionItem/MoneyRequestAction.tsx | 5 +++-- .../ReportActionItem/TaskPreview.tsx | 4 ++-- .../getReportRouteForCurrentContext.ts | 22 +++++++++++++++++++ src/libs/actions/Report/index.ts | 16 +++++++++++--- src/libs/actions/Task.ts | 7 +++++- src/pages/ReportDetailsPage.tsx | 7 +++--- src/pages/inbox/ReportNavigateAwayHandler.tsx | 5 +++++ src/pages/tasks/TaskAssigneeSelectorModal.tsx | 9 ++++++++ src/pages/tasks/TaskDescriptionPage.tsx | 10 +++++++++ src/pages/tasks/TaskTitlePage.tsx | 10 +++++++++ 10 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 src/libs/Navigation/helpers/getReportRouteForCurrentContext.ts diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 41d5249a7fbb..be2756832a87 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -10,6 +10,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {createTransactionThreadReport} from '@libs/actions/Report'; +import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; @@ -124,7 +125,7 @@ function MoneyRequestAction({ Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionThreadReport?.reportID, backTo: Navigation.getActiveRoute()})); return; } - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(transactionThreadReport?.reportID, undefined, undefined, Navigation.getActiveRoute())); + Navigation.navigate(getReportRouteForCurrentContext({reportID: transactionThreadReport?.reportID})); return; } @@ -133,7 +134,7 @@ function MoneyRequestAction({ return; } - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(action?.childReportID, undefined, undefined, Navigation.getActiveRoute())); + Navigation.navigate(getReportRouteForCurrentContext({reportID: action?.childReportID})); }; const isDeletedParentAction = isDeletedParentActionReportActionsUtils(action); diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index bea2c312ac95..20ef4123b6b7 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -26,12 +26,12 @@ import {canActionTask, completeTask, getTaskAssigneeAccountID, reopenTask} from import ControlSelection from '@libs/ControlSelection'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; +import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import Parser from '@libs/Parser'; import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils'; import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import type {Report, ReportAction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -138,7 +138,7 @@ function TaskPreview({ return ( Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID, undefined, undefined, Navigation.getActiveRoute()))} + onPress={() => Navigation.navigate(getReportRouteForCurrentContext({reportID: taskReportID}))} onPressIn={() => canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => diff --git a/src/libs/Navigation/helpers/getReportRouteForCurrentContext.ts b/src/libs/Navigation/helpers/getReportRouteForCurrentContext.ts new file mode 100644 index 000000000000..5b91b981db24 --- /dev/null +++ b/src/libs/Navigation/helpers/getReportRouteForCurrentContext.ts @@ -0,0 +1,22 @@ +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; +import isSearchTopmostFullScreenRoute from './isSearchTopmostFullScreenRoute'; + +type GetReportRouteForCurrentContextParams = { + reportID: string | undefined; + reportActionID?: string; + backTo?: Route; +}; + +function getReportRouteForCurrentContext({reportID, reportActionID, backTo}: GetReportRouteForCurrentContextParams): Route { + const currentRoute = backTo ?? (Navigation.getActiveRoute() as Route); + + if (isSearchTopmostFullScreenRoute()) { + return ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo: currentRoute}); + } + + return ROUTES.REPORT_WITH_ID.getRoute(reportID, reportActionID, undefined, currentRoute); +} + +export default getReportRouteForCurrentContext; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index da6c106c15aa..f9455e94d3b0 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -77,6 +77,7 @@ import HttpUtils from '@libs/HttpUtils'; import Log from '@libs/Log'; import {isEmailPublicDomain} from '@libs/LoginUtils'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; +import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; import type {LinkToOptions} from '@libs/Navigation/helpers/linkTo/types'; import Navigation from '@libs/Navigation/Navigation'; import enhanceParameters from '@libs/Network/enhanceParameters'; @@ -195,6 +196,7 @@ import type {OnboardingAccounting} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NewRoomForm'; import type { AnyRequest, @@ -2073,7 +2075,7 @@ function navigateToAndOpenChildReport( ) { const report = childReport ?? createChildReport(childReport, parentReportAction, parentReport, currentUserAccountID, introSelected, betas); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID, undefined, undefined, Navigation.getActiveRoute())); + Navigation.navigate(getReportRouteForCurrentContext({reportID: report.reportID})); } /** @@ -4297,7 +4299,13 @@ function navigateToMostRecentReport( currentUserAccountID: number, introSelected: OnyxEntry, betas: OnyxEntry, + backTo?: Route, ) { + if (backTo) { + Navigation.goBack(backTo); + return; + } + const lastAccessedReportID = findLastAccessedReport(false, false, currentReport?.reportID)?.reportID; if (lastAccessedReportID) { @@ -4349,6 +4357,7 @@ function leaveGroupChat( conciergeReportID: string | undefined, introSelected: OnyxEntry, betas: OnyxEntry, + backTo?: Route, ) { const reportID = report.reportID; // Use merge instead of set to avoid deleting the report too quickly, which could cause a brief "not found" page to appear. @@ -4396,7 +4405,7 @@ function leaveGroupChat( }, ]; - navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas); + navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas, backTo); API.write(WRITE_COMMANDS.LEAVE_GROUP_CHAT, {reportID}, {optimisticData, successData, failureData}); } @@ -4408,6 +4417,7 @@ function leaveRoom( introSelected: OnyxEntry, betas: OnyxEntry, isWorkspaceMemberLeavingWorkspaceRoom = false, + backTo?: Route, ) { const reportID = report.reportID; const isChatThread = isChatThreadReportUtils(report); @@ -4512,7 +4522,7 @@ function leaveRoom( return; } // In other cases, the report is deleted and we should move the user to another report. - navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas); + navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas, backTo); } function buildInviteToRoomOnyxData(report: Report, inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, formatPhoneNumber: LocaleContextProps['formatPhoneNumber']) { diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index c4443ca09185..b86c5d07d069 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -9,6 +9,7 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; import NetworkConnection from '@libs/NetworkConnection'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -375,7 +376,11 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { InteractionManager.runAfterInteractions(() => { clearOutTaskInfo(); }); - Navigation.dismissModalWithReport({reportID: parentReportID}); + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + } else { + Navigation.dismissModalWithReport({reportID: parentReportID}); + } } notifyNewAction(parentReportID, optimisticAddCommentReport.reportAction, true); } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index cda278b983ad..381812df118e 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -332,13 +332,13 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const leaveChat = useCallback(() => { if (isRootGroupChat) { - leaveGroupChat(report, quickAction?.chatReportID?.toString() === report.reportID, currentUserPersonalDetails.accountID, conciergeReportID, introSelected, betas); + leaveGroupChat(report, quickAction?.chatReportID?.toString() === report.reportID, currentUserPersonalDetails.accountID, conciergeReportID, introSelected, betas, backTo); return; } const isWorkspaceMemberLeavingWorkspaceRoom = isWorkspaceMemberLeavingWorkspaceRoomUtil(report, isPolicyEmployee, isPolicyAdmin); - leaveRoom(report, currentUserPersonalDetails.accountID, conciergeReportID, introSelected, betas, isWorkspaceMemberLeavingWorkspaceRoom); - }, [isRootGroupChat, isPolicyEmployee, isPolicyAdmin, quickAction?.chatReportID, report, currentUserPersonalDetails.accountID, conciergeReportID, introSelected, betas]); + leaveRoom(report, currentUserPersonalDetails.accountID, conciergeReportID, introSelected, betas, isWorkspaceMemberLeavingWorkspaceRoom, backTo); + }, [isRootGroupChat, isPolicyEmployee, isPolicyAdmin, quickAction?.chatReportID, report, currentUserPersonalDetails.accountID, conciergeReportID, introSelected, betas, backTo]); const showLastMemberLeavingModal = useCallback(async () => { const {action} = await showConfirmModal({ @@ -632,6 +632,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail iouTransactionID, moneyRequestReport?.reportID, currentUserPersonalDetails.accountID, + currentUserPersonalDetails.email, isTaskActionable, isRootGroupChat, leaveChat, diff --git a/src/pages/inbox/ReportNavigateAwayHandler.tsx b/src/pages/inbox/ReportNavigateAwayHandler.tsx index 1022dcab9fe9..696658039ca3 100644 --- a/src/pages/inbox/ReportNavigateAwayHandler.tsx +++ b/src/pages/inbox/ReportNavigateAwayHandler.tsx @@ -8,6 +8,7 @@ import useParentReportAction from '@hooks/useParentReportAction'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import {isDeletedParentAction} from '@libs/ReportActionsUtils'; @@ -204,6 +205,10 @@ function ReportNavigateAwayHandler() { // Fallback to Concierge Navigation.isNavigationReady().then(() => { + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas); }); }, [reportWasDeleted, isFocused, deletedReportParentID, conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas]); diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 3ca48090f11a..1f902538adc9 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -24,6 +24,7 @@ import {searchUserInServer} from '@libs/actions/Report'; import {canModifyTask, editTaskAssignee, setAssigneeValue} from '@libs/actions/Task'; import {READ_COMMANDS} from '@libs/API/types'; import HttpUtils from '@libs/HttpUtils'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import {getHeaderMessage, isCurrentUser} from '@libs/OptionsListUtils'; @@ -88,6 +89,10 @@ function TaskAssigneeSelectorModal() { const reportOnyx = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; if (reportOnyx && !isTaskReport(reportOnyx)) { Navigation.isNavigationReady().then(() => { + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } Navigation.dismissModalWithReport({reportID: reportOnyx.reportID}); }); } @@ -184,6 +189,10 @@ function TaskAssigneeSelectorModal() { } // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } Navigation.dismissModalWithReport({reportID: report?.reportID}); }); // If there's no report, we're creating a new task diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index 7c15678b12f4..f0f70ff757bc 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -17,6 +17,7 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDescriptionNavigatorParamList} from '@libs/Navigation/types'; @@ -62,6 +63,11 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti editTask(report, {description: values.description}, delegateEmail); } + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID: report?.reportID}); }, [report, delegateEmail], @@ -69,6 +75,10 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } Navigation.dismissModalWithReport({reportID: report?.reportID}); }); } diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index 0dd7c78efcc3..3e549e4d2e69 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -17,6 +17,7 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TaskDetailsNavigatorParamList} from '@libs/Navigation/types'; @@ -67,6 +68,11 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) editTask(report, {title: values.title}, delegateEmail); } + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID: report?.reportID}); }, [report, delegateEmail], @@ -74,6 +80,10 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } Navigation.dismissModalWithReport({reportID: report?.reportID}); }); } From 28cc6569609ac13053abe3dfd12ae5fdf00f43d1 Mon Sep 17 00:00:00 2001 From: marufsharifi Date: Tue, 14 Apr 2026 14:24:19 +0430 Subject: [PATCH 02/18] Refactor task modal dismiss logic into shared helper --- .../helpers/dismissModalForCurrentContext.ts | 18 ++++++++++++++++++ src/libs/actions/Task.ts | 8 ++------ src/pages/tasks/TaskAssigneeSelectorModal.tsx | 14 +++----------- src/pages/tasks/TaskDescriptionPage.tsx | 15 +++------------ src/pages/tasks/TaskTitlePage.tsx | 15 +++------------ 5 files changed, 29 insertions(+), 41 deletions(-) create mode 100644 src/libs/Navigation/helpers/dismissModalForCurrentContext.ts diff --git a/src/libs/Navigation/helpers/dismissModalForCurrentContext.ts b/src/libs/Navigation/helpers/dismissModalForCurrentContext.ts new file mode 100644 index 000000000000..13251710862b --- /dev/null +++ b/src/libs/Navigation/helpers/dismissModalForCurrentContext.ts @@ -0,0 +1,18 @@ +import Navigation from '@libs/Navigation/Navigation'; +import isSearchTopmostFullScreenRoute from './isSearchTopmostFullScreenRoute'; + +function dismissModalForCurrentContext(reportID?: string) { + if (isSearchTopmostFullScreenRoute()) { + Navigation.dismissModal(); + return; + } + + if (!reportID) { + Navigation.dismissModal(); + return; + } + + Navigation.dismissModalWithReport({reportID}); +} + +export default dismissModalForCurrentContext; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 02c3fef94988..d9828633a184 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -9,7 +9,7 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; -import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; +import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import NetworkConnection from '@libs/NetworkConnection'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -376,11 +376,7 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { InteractionManager.runAfterInteractions(() => { clearOutTaskInfo(); }); - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - } else { - Navigation.dismissModalWithReport({reportID: parentReportID}); - } + dismissModalForCurrentContext(parentReportID); } notifyNewAction(parentReportID, optimisticAddCommentReport.reportAction, true); } diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 1f902538adc9..e07f6ecef94f 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -24,7 +24,7 @@ import {searchUserInServer} from '@libs/actions/Report'; import {canModifyTask, editTaskAssignee, setAssigneeValue} from '@libs/actions/Task'; import {READ_COMMANDS} from '@libs/API/types'; import HttpUtils from '@libs/HttpUtils'; -import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; +import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import {getHeaderMessage, isCurrentUser} from '@libs/OptionsListUtils'; @@ -89,11 +89,7 @@ function TaskAssigneeSelectorModal() { const reportOnyx = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; if (reportOnyx && !isTaskReport(reportOnyx)) { Navigation.isNavigationReady().then(() => { - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - return; - } - Navigation.dismissModalWithReport({reportID: reportOnyx.reportID}); + dismissModalForCurrentContext(reportOnyx.reportID); }); } return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; @@ -189,11 +185,7 @@ function TaskAssigneeSelectorModal() { } // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - return; - } - Navigation.dismissModalWithReport({reportID: report?.reportID}); + dismissModalForCurrentContext(report?.reportID); }); // If there's no report, we're creating a new task } else if (option.accountID) { diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index f0f70ff757bc..f9394260b9b4 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -17,7 +17,7 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; -import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; +import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDescriptionNavigatorParamList} from '@libs/Navigation/types'; @@ -63,23 +63,14 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti editTask(report, {description: values.description}, delegateEmail); } - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - return; - } - - Navigation.dismissModalWithReport({reportID: report?.reportID}); + dismissModalForCurrentContext(report?.reportID); }, [report, delegateEmail], ); if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - return; - } - Navigation.dismissModalWithReport({reportID: report?.reportID}); + dismissModalForCurrentContext(report?.reportID); }); } const inputRef = useRef(null); diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index 3e549e4d2e69..bea3dab188b4 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -17,7 +17,7 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; -import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; +import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TaskDetailsNavigatorParamList} from '@libs/Navigation/types'; @@ -68,23 +68,14 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) editTask(report, {title: values.title}, delegateEmail); } - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - return; - } - - Navigation.dismissModalWithReport({reportID: report?.reportID}); + dismissModalForCurrentContext(report?.reportID); }, [report, delegateEmail], ); if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - if (isSearchTopmostFullScreenRoute()) { - Navigation.dismissModal(); - return; - } - Navigation.dismissModalWithReport({reportID: report?.reportID}); + dismissModalForCurrentContext(report?.reportID); }); } From 225c9c2ee45bf5fc42133b5a4706a2b201c8c2dd Mon Sep 17 00:00:00 2001 From: marufsharifi Date: Tue, 14 Apr 2026 15:13:34 +0430 Subject: [PATCH 03/18] Guard leave flow backTo against current report --- .../helpers/shouldUseBackToOnLeaveReport.ts | 35 +++++++++++++++++++ src/libs/actions/Report/index.ts | 3 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts diff --git a/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts b/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts new file mode 100644 index 000000000000..9f8881978985 --- /dev/null +++ b/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts @@ -0,0 +1,35 @@ +import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; + +function normalizeRoute(route: string): string { + return route + .replaceAll(/\?.*/g, '') + .replaceAll(/^\/+|\/+$/g, '') + .replaceAll(/\/+/g, '/'); +} + +function doesRouteTargetCurrentReport(route: string, reportID: string): boolean { + const normalizedRoute = normalizeRoute(route); + const currentReportRoutes = [ + ROUTES.REPORT_WITH_ID.getRoute(reportID), + ROUTES.SEARCH_REPORT.getRoute({reportID}), + ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID}), + ROUTES.EXPENSE_REPORT_RHP.getRoute({reportID}), + ].map(normalizeRoute); + + return currentReportRoutes.some((currentReportRoute) => normalizedRoute === currentReportRoute || normalizedRoute.startsWith(`${currentReportRoute}/`)); +} + +function shouldUseBackToOnLeaveReport(reportID: string | undefined, backTo?: Route): boolean { + if (!backTo) { + return false; + } + + if (!reportID) { + return true; + } + + return !doesRouteTargetCurrentReport(backTo, reportID); +} + +export default shouldUseBackToOnLeaveReport; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index f9455e94d3b0..3a047255d21d 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -79,6 +79,7 @@ import {isEmailPublicDomain} from '@libs/LoginUtils'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; import type {LinkToOptions} from '@libs/Navigation/helpers/linkTo/types'; +import shouldUseBackToOnLeaveReport from '@libs/Navigation/helpers/shouldUseBackToOnLeaveReport'; import Navigation from '@libs/Navigation/Navigation'; import enhanceParameters from '@libs/Network/enhanceParameters'; import * as NetworkStore from '@libs/Network/NetworkStore'; @@ -4301,7 +4302,7 @@ function navigateToMostRecentReport( betas: OnyxEntry, backTo?: Route, ) { - if (backTo) { + if (shouldUseBackToOnLeaveReport(currentReport?.reportID, backTo)) { Navigation.goBack(backTo); return; } From 315fd72bc56de47b2720ce705de7558ac008301b Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 23 Apr 2026 16:05:24 +0430 Subject: [PATCH 04/18] Return to task after edits --- src/pages/tasks/TaskAssigneeSelectorModal.tsx | 5 ++--- src/pages/tasks/TaskDescriptionPage.tsx | 7 +++---- src/pages/tasks/TaskTitlePage.tsx | 7 +++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index e07f6ecef94f..00f5f6e3fd8a 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -24,7 +24,6 @@ import {searchUserInServer} from '@libs/actions/Report'; import {canModifyTask, editTaskAssignee, setAssigneeValue} from '@libs/actions/Task'; import {READ_COMMANDS} from '@libs/API/types'; import HttpUtils from '@libs/HttpUtils'; -import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import {getHeaderMessage, isCurrentUser} from '@libs/OptionsListUtils'; @@ -89,7 +88,7 @@ function TaskAssigneeSelectorModal() { const reportOnyx = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; if (reportOnyx && !isTaskReport(reportOnyx)) { Navigation.isNavigationReady().then(() => { - dismissModalForCurrentContext(reportOnyx.reportID); + Navigation.goBack(backTo); }); } return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; @@ -185,7 +184,7 @@ function TaskAssigneeSelectorModal() { } // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - dismissModalForCurrentContext(report?.reportID); + Navigation.goBack(backTo); }); // If there's no report, we're creating a new task } else if (option.accountID) { diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index f9394260b9b4..6a8542bd02be 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -17,7 +17,6 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; -import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDescriptionNavigatorParamList} from '@libs/Navigation/types'; @@ -63,14 +62,14 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti editTask(report, {description: values.description}, delegateEmail); } - dismissModalForCurrentContext(report?.reportID); + Navigation.goBack(route.params.backTo); }, - [report, delegateEmail], + [report, delegateEmail, route.params.backTo], ); if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - dismissModalForCurrentContext(report?.reportID); + Navigation.goBack(route.params.backTo); }); } const inputRef = useRef(null); diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index bea3dab188b4..59dfc3e09090 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -17,7 +17,6 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; -import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TaskDetailsNavigatorParamList} from '@libs/Navigation/types'; @@ -68,14 +67,14 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) editTask(report, {title: values.title}, delegateEmail); } - dismissModalForCurrentContext(report?.reportID); + Navigation.goBack(route.params.backTo); }, - [report, delegateEmail], + [report, delegateEmail, route.params.backTo], ); if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - dismissModalForCurrentContext(report?.reportID); + Navigation.goBack(route.params.backTo); }); } From a8b07e2cf80dc9facf5bf557ff3ad5a1cc6ff0e1 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 23 Apr 2026 16:48:50 +0430 Subject: [PATCH 05/18] Unwrap search backTo on leave --- .../helpers/shouldUseBackToOnLeaveReport.ts | 45 +++++++++++++++++-- src/libs/actions/Report/index.ts | 8 ++-- .../shouldUseBackToOnLeaveReport.test.ts | 33 ++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts diff --git a/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts b/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts index 9f8881978985..dad0ff1ff178 100644 --- a/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts +++ b/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts @@ -8,6 +8,23 @@ function normalizeRoute(route: string): string { .replaceAll(/\/+/g, '/'); } +function getNestedBackToRoute(route: string): Route | undefined { + const [, queryString = ''] = route.split('?'); + + if (!queryString) { + return undefined; + } + + const params = new URLSearchParams(queryString); + const encodedBackTo = params.get('backTo'); + + if (!encodedBackTo) { + return undefined; + } + + return decodeURIComponent(encodedBackTo) as Route; +} + function doesRouteTargetCurrentReport(route: string, reportID: string): boolean { const normalizedRoute = normalizeRoute(route); const currentReportRoutes = [ @@ -20,16 +37,36 @@ function doesRouteTargetCurrentReport(route: string, reportID: string): boolean return currentReportRoutes.some((currentReportRoute) => normalizedRoute === currentReportRoute || normalizedRoute.startsWith(`${currentReportRoute}/`)); } -function shouldUseBackToOnLeaveReport(reportID: string | undefined, backTo?: Route): boolean { +function getBackToOnLeaveReport(reportID: string | undefined, backTo?: Route): Route | undefined { if (!backTo) { - return false; + return undefined; + } + + const normalizedBackTo = normalizeRoute(backTo); + const isSearchRoute = normalizedBackTo.startsWith(ROUTES.SEARCH_ROOT.route); + + if (isSearchRoute) { + if (!reportID || !doesRouteTargetCurrentReport(backTo, reportID)) { + return backTo; + } + + return getNestedBackToRoute(backTo) ?? backTo; } if (!reportID) { - return true; + return backTo; } - return !doesRouteTargetCurrentReport(backTo, reportID); + if (doesRouteTargetCurrentReport(backTo, reportID)) { + return undefined; + } + + return backTo; +} + +function shouldUseBackToOnLeaveReport(reportID: string | undefined, backTo?: Route): boolean { + return !!getBackToOnLeaveReport(reportID, backTo); } +export {getBackToOnLeaveReport}; export default shouldUseBackToOnLeaveReport; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 2dee5b3c93dd..ff1fa6e5dc8d 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -79,7 +79,7 @@ import {isEmailPublicDomain} from '@libs/LoginUtils'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; import type {LinkToOptions} from '@libs/Navigation/helpers/linkTo/types'; -import shouldUseBackToOnLeaveReport from '@libs/Navigation/helpers/shouldUseBackToOnLeaveReport'; +import {getBackToOnLeaveReport} from '@libs/Navigation/helpers/shouldUseBackToOnLeaveReport'; import Navigation from '@libs/Navigation/Navigation'; import enhanceParameters from '@libs/Network/enhanceParameters'; import {getDBTimeWithSkew, getIsOffline as isOfflineNetwork} from '@libs/NetworkState'; @@ -4420,8 +4420,10 @@ function navigateToMostRecentReport( betas: OnyxEntry, backTo?: Route, ) { - if (shouldUseBackToOnLeaveReport(currentReport?.reportID, backTo)) { - Navigation.goBack(backTo); + const backToOnLeave = getBackToOnLeaveReport(currentReport?.reportID, backTo); + + if (backToOnLeave) { + Navigation.goBack(backToOnLeave); return; } diff --git a/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts b/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts new file mode 100644 index 000000000000..ca4814848b0b --- /dev/null +++ b/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts @@ -0,0 +1,33 @@ +import shouldUseBackToOnLeaveReport, {getBackToOnLeaveReport} from '@libs/Navigation/helpers/shouldUseBackToOnLeaveReport'; +import ROUTES from '@src/ROUTES'; + +describe('shouldUseBackToOnLeaveReport', () => { + const reportID = '123'; + + it('preserves a Search route even when it targets the current report', () => { + const backTo = ROUTES.SEARCH_REPORT.getRoute({ + reportID, + reportActionID: '456', + backTo: ROUTES.SEARCH_ROOT.getRoute({query: 'type:chat', rawQuery: 'type:chat'}), + }); + + expect(shouldUseBackToOnLeaveReport(reportID, backTo)).toBe(true); + }); + + it('unwraps nested Search backTo when the immediate Search route targets the current report', () => { + const nestedBackTo = ROUTES.SEARCH_ROOT.getRoute({query: 'type:chat', rawQuery: 'type:chat'}); + const backTo = ROUTES.SEARCH_REPORT.getRoute({ + reportID, + reportActionID: '456', + backTo: nestedBackTo, + }); + + expect(getBackToOnLeaveReport(reportID, backTo)).toBe(decodeURIComponent(nestedBackTo)); + }); + + it('does not preserve an Inbox route that points back to the current report', () => { + const backTo = ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, ROUTES.REPORT_WITH_ID.getRoute(reportID)); + + expect(shouldUseBackToOnLeaveReport(reportID, backTo)).toBe(false); + }); +}); From 5ff28e6ac0904f9c5c0e6381e65ca2713cd2b398 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 27 Apr 2026 17:33:25 +0430 Subject: [PATCH 06/18] Preserve backTo when deleting tasks --- src/libs/actions/Task.ts | 11 ++++++-- src/pages/ReportDetailsPage.tsx | 31 +++++++++++++++++++-- tests/actions/TaskTest.ts | 49 +++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 365f90cc5ec6..9cbd5eae42c6 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1149,7 +1149,7 @@ function getShareDestination( * @param report - The task report being deleted * @returns The URL to navigate to */ -function getNavigationUrlOnTaskDelete(report: OnyxEntry, conciergeReportID: string | undefined): string | undefined { +function getNavigationUrlOnTaskDelete(report: OnyxEntry, conciergeReportID: string | undefined, backTo?: Route): Route | undefined { if (!report) { return undefined; } @@ -1159,6 +1159,10 @@ function getNavigationUrlOnTaskDelete(report: OnyxEntry, conci return undefined; } + if (backTo) { + return backTo; + } + if (report?.parentReportID) { return ROUTES.REPORT_WITH_ID.getRoute(report.parentReportID); } @@ -1185,6 +1189,7 @@ function deleteTask( conciergeReportID: string | undefined, delegateEmail: string | undefined, ancestors: ReportUtils.Ancestor[] = [], + backTo?: Route, ) { if (!report) { return; @@ -1309,9 +1314,9 @@ function deleteTask( API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); notifyNewAction(report.reportID, undefined, true); - const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID); + const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo); if (urlToNavigateBack) { - Navigation.goBack(); + Navigation.goBack(urlToNavigateBack); return urlToNavigateBack; } } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index cca762498ede..e7cbc86401c7 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -125,7 +125,7 @@ import { updateGroupChatAvatar, } from '@userActions/Report'; import {callFunctionIfActionIsAllowed} from '@userActions/Session'; -import {canActionTask, canModifyTask, deleteTask, reopenTask} from '@userActions/Task'; +import {canActionTask, canModifyTask, deleteTask, getNavigationUrlOnTaskDelete, reopenTask} from '@userActions/Task'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -914,6 +914,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail conciergeReportID, delegateEmail, ancestors, + backTo, ); return; } @@ -973,6 +974,15 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail // Where to navigate back to after deleting the transaction and its report. const navigateToTargetUrl = useCallback(() => { + if (caseID === CASES.DEFAULT) { + const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo); + + if (urlToNavigateBack) { + setDeleteTransactionNavigateBackUrl(urlToNavigateBack); + } + return; + } + let urlToNavigateBack: string | undefined; // Only proceed with navigation logic if transaction was actually deleted if (!isEmptyObject(requestParentReportAction)) { @@ -1037,7 +1047,20 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail setDeleteTransactionNavigateBackUrl(urlToNavigateBack); navigateBackOnDeleteTransaction(urlToNavigateBack as Route); } - }, [requestParentReportAction, route.params.reportID, moneyRequestReport, iouTransactionID, iouReport, chatIOUReport, isChatIOUReportArchived, isSingleTransactionView]); + }, [ + backTo, + caseID, + chatIOUReport, + conciergeReportID, + iouReport, + iouTransactionID, + isChatIOUReportArchived, + isSingleTransactionView, + moneyRequestReport, + report, + requestParentReportAction, + route.params.reportID, + ]); const showDeleteModal = useCallback(async () => { const {action} = await showConfirmModal({ @@ -1052,6 +1075,10 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail return; } Navigation.setNavigationActionToMicrotaskQueue(() => { + if (caseID === CASES.DEFAULT) { + deleteTransaction(); + return; + } navigateToTargetUrl(); // Delay deletion until the RHP close animation finishes to prevent a brief // "Not Found" flash inside the animating-out panel on slower devices. diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index da42e8d7519c..ff5bc2cc983b 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -32,6 +32,7 @@ import initOnyxDerivedValues from '@userActions/OnyxDerived'; import CONST from '@src/CONST'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route} from '@src/ROUTES'; import type {Report, ReportAction} from '@src/types/onyx'; import type {OnyxData} from '@src/types/onyx/Request'; import {getFakeReport, getFakeReportAction} from '../utils/LHNTestUtils'; @@ -1029,6 +1030,16 @@ describe('actions/Task', () => { expect(result).toBe(`r/${parentReportID}`); }); + it('should prefer backTo when report has no visible actions', () => { + const parentReportID = 'parent_123'; + const taskReport = {...getFakeReport(), parentReportID}; + const backTo = '/search' as Route; + doesReportHaveVisibleActionsSpy.mockReturnValue(false); + + const result = getNavigationUrlOnTaskDelete(taskReport, 'concierge_123', backTo); + expect(result).toBe(backTo); + }); + it('should return most recent report route when no parentReportID and getMostRecentReportID returns a value', () => { const taskReport = {...getFakeReport(), parentReportID: undefined}; const mostRecentReportID = 'recent_456'; @@ -1359,7 +1370,41 @@ describe('actions/Task', () => { const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined); expect(result).toBe(`r/${parentReportID}`); - expect(Navigation.goBack).toHaveBeenCalled(); + expect(Navigation.goBack).toHaveBeenCalledWith(`r/${parentReportID}`); + }); + + it('should navigate back to backTo when provided and the task report is deleted', async () => { + const taskReportID = 'task_report_delete_back_to'; + const parentReportID = 'parent_report_delete_back_to'; + const backTo = '/search' as Route; + + const taskReport = { + reportID: taskReportID, + type: CONST.REPORT.TYPE.TASK, + reportName: 'Task From Search', + parentReportID, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + ownerAccountID: mockCurrentUserAccountID, + }; + + const parentReport = { + reportID: parentReportID, + type: CONST.REPORT.TYPE.CHAT, + }; + + await act(async () => { + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, taskReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, parentReport); + }); + await waitForBatchedUpdatesWithAct(); + + doesReportHaveVisibleActionsSpy.mockReturnValue(false); + + const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, [], backTo); + + expect(result).toBe(backTo); + expect(Navigation.goBack).toHaveBeenCalledWith(backTo); }); it('should return conciergeReportID-based URL when no parentReportID and no recent report', async () => { @@ -1388,7 +1433,7 @@ describe('actions/Task', () => { expect(result).toBe(`r/${conciergeReportID}`); expect(getMostRecentReportIDSpy).toHaveBeenCalledWith(taskReport, conciergeReportID); - expect(Navigation.goBack).toHaveBeenCalled(); + expect(Navigation.goBack).toHaveBeenCalledWith(`r/${conciergeReportID}`); }); it('should not return navigation URL when task report has visible actions', async () => { From 5ad11150b74d89a048ea0d0d9eb311a4481c1eaa Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Wed, 29 Apr 2026 15:17:39 +0430 Subject: [PATCH 07/18] fix task delete navigation from search --- src/libs/actions/Task.ts | 65 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 9cbd5eae42c6..f9b1bd237921 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1144,6 +1144,69 @@ function getShareDestination( }; } +function getNestedBackToRoute(route: Route): Route | undefined { + const queryStringIndex = route.indexOf('?'); + const queryString = queryStringIndex === -1 ? '' : route.slice(queryStringIndex + 1); + + if (!queryString) { + return undefined; + } + + const params = new URLSearchParams(queryString); + const encodedBackTo = params.get('backTo'); + + if (!encodedBackTo) { + return undefined; + } + + return encodedBackTo as Route; +} + +function getSearchRouteWithoutReportActionID(route: Route): Route { + const queryStringIndex = route.indexOf('?'); + const path = queryStringIndex === -1 ? route : route.slice(0, queryStringIndex); + const queryString = queryStringIndex === -1 ? '' : route.slice(queryStringIndex + 1); + const match = path.match(/^\/?search\/view\/([^/?]+)\/[^/?]+\/?$/); + + if (!match) { + return route; + } + + const reportID = match.at(1); + if (!reportID) { + return route; + } + + const cleanedRoute = ROUTES.SEARCH_REPORT.getRoute({reportID}); + if (!queryString) { + return cleanedRoute; + } + + return `${cleanedRoute}?${queryString}` as Route; +} + +function getFlattenedTaskDeleteBackTo(backTo: Route): Route { + const flattenedBackTo = getSearchRouteWithoutReportActionID(backTo); + const nestedBackTo = getNestedBackToRoute(flattenedBackTo); + + if (!nestedBackTo) { + return flattenedBackTo; + } + + const flattenedNestedBackTo = getSearchRouteWithoutReportActionID(nestedBackTo); + if (flattenedNestedBackTo === nestedBackTo) { + return flattenedBackTo; + } + + const queryStringIndex = flattenedBackTo.indexOf('?'); + const path = queryStringIndex === -1 ? flattenedBackTo : flattenedBackTo.slice(0, queryStringIndex); + const queryString = queryStringIndex === -1 ? '' : flattenedBackTo.slice(queryStringIndex + 1); + const params = new URLSearchParams(queryString); + params.set('backTo', flattenedNestedBackTo); + + return `${path}?${params.toString()}` as Route; +} + /** * Calculate the URL to navigate to after a task deletion * @param report - The task report being deleted @@ -1160,7 +1223,7 @@ function getNavigationUrlOnTaskDelete(report: OnyxEntry, conci } if (backTo) { - return backTo; + return getFlattenedTaskDeleteBackTo(backTo); } if (report?.parentReportID) { From ab0039379adce8ef14b8747b9a7ac3d4b39bd6ea Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 4 May 2026 12:53:34 +0430 Subject: [PATCH 08/18] fix lint --- src/pages/ReportDetailsPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 0c7540f512d2..8da05179499f 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -976,6 +976,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading removeTransaction, conciergeReportID, delegateEmail, + backTo, ]); // Where to navigate back to after deleting the transaction and its report. From 76de24984ac328c2cec5bf982489aaa89ef16dbc Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 5 May 2026 10:47:58 +0430 Subject: [PATCH 09/18] Fix task delete navigation when task has messages --- src/libs/actions/Task.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 1192e5835c59..505604e52b0b 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1223,11 +1223,6 @@ function getNavigationUrlOnTaskDelete(report: OnyxEntry, conci return undefined; } - const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID); - if (!shouldDeleteTaskReport) { - return undefined; - } - if (backTo) { return getFlattenedTaskDeleteBackTo(backTo); } From dd5cb71aa0eba473ec412b7be226a56f049d7099 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 5 May 2026 11:08:33 +0430 Subject: [PATCH 10/18] Update task delete tests --- tests/actions/TaskTest.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index af6e1a19bb32..033945c5600d 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -1006,11 +1006,20 @@ describe('actions/Task', () => { expect(getNavigationUrlOnTaskDelete(undefined, 'concierge_123')).toBeUndefined(); }); - it('should return undefined when report has visible actions (should not delete)', () => { + it('should return parent report route when report has visible actions and a parent report exists', () => { + const parentReportID = 'parent_123'; + const taskReport = {...getFakeReport(), parentReportID}; + doesReportHaveVisibleActionsSpy.mockReturnValue(true); + + expect(getNavigationUrlOnTaskDelete(taskReport, 'concierge_123')).toBe(`r/${parentReportID}`); + }); + + it('should prefer backTo when report has visible actions', () => { const taskReport = getFakeReport(); + const backTo = '/search' as Route; doesReportHaveVisibleActionsSpy.mockReturnValue(true); - expect(getNavigationUrlOnTaskDelete(taskReport, 'concierge_123')).toBeUndefined(); + expect(getNavigationUrlOnTaskDelete(taskReport, 'concierge_123', backTo)).toBe(backTo); }); it('should return parent report route when report has parentReportID and no visible actions', () => { @@ -1428,7 +1437,7 @@ describe('actions/Task', () => { expect(Navigation.goBack).toHaveBeenCalledWith(`r/${conciergeReportID}`); }); - it('should not return navigation URL when task report has visible actions', async () => { + it('should navigate away when task report has visible actions', async () => { const taskReportID = 'task_report_delete_4'; const parentReportID = 'parent_report_delete_4'; @@ -1453,13 +1462,13 @@ describe('actions/Task', () => { }); await waitForBatchedUpdatesWithAct(); - // Has visible actions, so should not navigate away + // Even when the task has visible actions, deleting it should still leave the task view. doesReportHaveVisibleActionsSpy.mockReturnValue(true); const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined); - expect(result).toBeUndefined(); - expect(Navigation.goBack).not.toHaveBeenCalled(); + expect(result).toBe(`r/${parentReportID}`); + expect(Navigation.goBack).toHaveBeenCalledWith(`r/${parentReportID}`); }); it('should return undefined when no parentReportID, no recent report, and conciergeReportID is undefined', async () => { From 872c4137a7b621a055403afee1b244a35b37c845 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 12 May 2026 10:07:08 +0430 Subject: [PATCH 11/18] fix lint --- src/libs/actions/Report/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 31ddeb07c552..389f6f9430f3 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -81,7 +81,6 @@ import {isEmailPublicDomain} from '@libs/LoginUtils'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; -import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import type {LinkToOptions} from '@libs/Navigation/helpers/linkTo/types'; import {getBackToOnLeaveReport} from '@libs/Navigation/helpers/shouldUseBackToOnLeaveReport'; import Navigation from '@libs/Navigation/Navigation'; From 5f7850d554d3dec4ddf18b90426ba52b7118a996 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 14 May 2026 10:50:56 +0430 Subject: [PATCH 12/18] fix back navigation after deleting task details --- src/libs/actions/Task.ts | 12 +++++++++--- src/pages/ReportDetailsPage.tsx | 4 +++- tests/actions/TaskTest.ts | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 1cfe38b85ecf..6f7932d64270 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -80,6 +80,11 @@ type CreateTaskAndNavigateParams = { currentUserAvatar: AvatarSource | undefined; }; +type DeleteTaskOptions = { + backTo?: Route; + shouldNavigateBack?: boolean; +}; + /** * Clears out the task info from the store */ @@ -1263,11 +1268,12 @@ function deleteTask( conciergeReportID: string | undefined, delegateEmail: string | undefined, ancestors: ReportUtils.Ancestor[] = [], - backTo?: Route, + options?: DeleteTaskOptions, ) { if (!report) { return; } + const {backTo, shouldNavigateBack = true} = options ?? {}; const message = `deleted task: ${report.reportName}`; const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(report.reportID, CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED, delegateEmail, message); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; @@ -1389,10 +1395,10 @@ function deleteTask( notifyNewAction(report.reportID, undefined, true); const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo); - if (urlToNavigateBack) { + if (urlToNavigateBack && shouldNavigateBack) { Navigation.goBack(urlToNavigateBack); - return urlToNavigateBack; } + return urlToNavigateBack; } /** diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 4bac37c2e20a..d5e64871c0ab 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -861,7 +861,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading conciergeReportID, delegateEmail, ancestors, - backTo, + {backTo, shouldNavigateBack: false}, ); return; } @@ -928,6 +928,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading if (urlToNavigateBack) { setDeleteTransactionNavigateBackUrl(urlToNavigateBack); + navigateBackOnDeleteTransaction(urlToNavigateBack); } return; } @@ -1025,6 +1026,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading } Navigation.setNavigationActionToMicrotaskQueue(() => { if (caseID === CASES.DEFAULT) { + navigateToTargetUrl(); deleteTransaction(); return; } diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index c15aae83bff0..59c03f550127 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -1542,7 +1542,7 @@ describe('actions/Task', () => { doesReportHaveVisibleActionsSpy.mockReturnValue(false); - const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, [], backTo); + const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, [], {backTo}); expect(result).toBe(backTo); expect(Navigation.goBack).toHaveBeenCalledWith(backTo); From 308ef5df453d2c7a965e90c89c9495130d52e724 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 14 May 2026 11:08:38 +0430 Subject: [PATCH 13/18] fix nested backTo handling when leaving thread --- src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts | 2 +- tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts b/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts index dad0ff1ff178..24a93a78905f 100644 --- a/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts +++ b/src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts @@ -22,7 +22,7 @@ function getNestedBackToRoute(route: string): Route | undefined { return undefined; } - return decodeURIComponent(encodedBackTo) as Route; + return encodedBackTo as Route; } function doesRouteTargetCurrentReport(route: string, reportID: string): boolean { diff --git a/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts b/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts index ca4814848b0b..e13b177fbdf3 100644 --- a/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts +++ b/tests/unit/Navigation/shouldUseBackToOnLeaveReport.test.ts @@ -22,7 +22,7 @@ describe('shouldUseBackToOnLeaveReport', () => { backTo: nestedBackTo, }); - expect(getBackToOnLeaveReport(reportID, backTo)).toBe(decodeURIComponent(nestedBackTo)); + expect(getBackToOnLeaveReport(reportID, backTo)).toBe(nestedBackTo); }); it('does not preserve an Inbox route that points back to the current report', () => { From 270d54bbfe94475b37ec636b1b872933a8def538 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 18 May 2026 15:37:46 +0430 Subject: [PATCH 14/18] fix task delete redirect after leaving details --- src/libs/actions/Task.ts | 12 +++--------- src/pages/ReportDetailsPage.tsx | 4 +--- tests/actions/TaskTest.ts | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 6f7932d64270..1cfe38b85ecf 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -80,11 +80,6 @@ type CreateTaskAndNavigateParams = { currentUserAvatar: AvatarSource | undefined; }; -type DeleteTaskOptions = { - backTo?: Route; - shouldNavigateBack?: boolean; -}; - /** * Clears out the task info from the store */ @@ -1268,12 +1263,11 @@ function deleteTask( conciergeReportID: string | undefined, delegateEmail: string | undefined, ancestors: ReportUtils.Ancestor[] = [], - options?: DeleteTaskOptions, + backTo?: Route, ) { if (!report) { return; } - const {backTo, shouldNavigateBack = true} = options ?? {}; const message = `deleted task: ${report.reportName}`; const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(report.reportID, CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED, delegateEmail, message); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; @@ -1395,10 +1389,10 @@ function deleteTask( notifyNewAction(report.reportID, undefined, true); const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo); - if (urlToNavigateBack && shouldNavigateBack) { + if (urlToNavigateBack) { Navigation.goBack(urlToNavigateBack); + return urlToNavigateBack; } - return urlToNavigateBack; } /** diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index d5e64871c0ab..4bac37c2e20a 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -861,7 +861,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading conciergeReportID, delegateEmail, ancestors, - {backTo, shouldNavigateBack: false}, + backTo, ); return; } @@ -928,7 +928,6 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading if (urlToNavigateBack) { setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - navigateBackOnDeleteTransaction(urlToNavigateBack); } return; } @@ -1026,7 +1025,6 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading } Navigation.setNavigationActionToMicrotaskQueue(() => { if (caseID === CASES.DEFAULT) { - navigateToTargetUrl(); deleteTransaction(); return; } diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index 59c03f550127..c15aae83bff0 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -1542,7 +1542,7 @@ describe('actions/Task', () => { doesReportHaveVisibleActionsSpy.mockReturnValue(false); - const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, [], {backTo}); + const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, [], backTo); expect(result).toBe(backTo); expect(Navigation.goBack).toHaveBeenCalledWith(backTo); From 92e782517b4541916766373c991d42290c8cd391 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 18 May 2026 18:26:21 +0430 Subject: [PATCH 15/18] fix task delete navigation when task has messages --- src/libs/actions/Task.ts | 9 +++++++-- src/pages/ReportDetailsPage.tsx | 6 +++--- tests/actions/TaskTest.ts | 12 ++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 1cfe38b85ecf..cb33d2aaa813 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1388,9 +1388,14 @@ function deleteTask( API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); notifyNewAction(report.reportID, undefined, true); - const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo); + const shouldNavigateAfterDelete = !!backTo || shouldDeleteTaskReport; + const urlToNavigateBack = shouldNavigateAfterDelete ? getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo) : undefined; if (urlToNavigateBack) { - Navigation.goBack(urlToNavigateBack); + if (backTo) { + Navigation.goBack(urlToNavigateBack, {compareParams: false}); + } else { + Navigation.goBack(); + } return urlToNavigateBack; } } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 4bac37c2e20a..00784636e0c8 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -923,7 +923,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading // Where to navigate back to after deleting the transaction and its report. const navigateToTargetUrl = useCallback(() => { - if (caseID === CASES.DEFAULT) { + if (caseID === CASES.DEFAULT && backTo) { const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo); if (urlToNavigateBack) { @@ -1024,7 +1024,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading return; } Navigation.setNavigationActionToMicrotaskQueue(() => { - if (caseID === CASES.DEFAULT) { + if (caseID === CASES.DEFAULT && backTo) { deleteTransaction(); return; } @@ -1035,7 +1035,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading deleteTransaction(); }); }); - }, [showConfirmModal, translate, caseID, navigateToTargetUrl, deleteTransaction]); + }, [showConfirmModal, translate, caseID, navigateToTargetUrl, deleteTransaction, backTo]); const mentionReportContextValue = useMemo(() => ({currentReportID: report.reportID, exactlyMatch: true}), [report.reportID]); diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index c15aae83bff0..c387769bb984 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -1511,7 +1511,7 @@ describe('actions/Task', () => { const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined); expect(result).toBe(`r/${parentReportID}`); - expect(Navigation.goBack).toHaveBeenCalledWith(`r/${parentReportID}`); + expect(Navigation.goBack).toHaveBeenCalledWith(); }); it('should navigate back to backTo when provided and the task report is deleted', async () => { @@ -1545,7 +1545,7 @@ describe('actions/Task', () => { const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, [], backTo); expect(result).toBe(backTo); - expect(Navigation.goBack).toHaveBeenCalledWith(backTo); + expect(Navigation.goBack).toHaveBeenCalledWith(backTo, {compareParams: false}); }); it('should return conciergeReportID-based URL when no parentReportID and no recent report', async () => { @@ -1574,7 +1574,7 @@ describe('actions/Task', () => { expect(result).toBe(`r/${conciergeReportID}`); expect(getMostRecentReportIDSpy).toHaveBeenCalledWith(taskReport, conciergeReportID); - expect(Navigation.goBack).toHaveBeenCalledWith(`r/${conciergeReportID}`); + expect(Navigation.goBack).toHaveBeenCalledWith(); }); it('should navigate away when task report has visible actions', async () => { @@ -1602,13 +1602,13 @@ describe('actions/Task', () => { }); await waitForBatchedUpdatesWithAct(); - // Even when the task has visible actions, deleting it should still leave the task view. + // When the task still has visible actions and there's no backTo, stay on the deleted task page. doesReportHaveVisibleActionsSpy.mockReturnValue(true); const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined); - expect(result).toBe(`r/${parentReportID}`); - expect(Navigation.goBack).toHaveBeenCalledWith(`r/${parentReportID}`); + expect(result).toBeUndefined(); + expect(Navigation.goBack).not.toHaveBeenCalled(); }); it('should return undefined when no parentReportID, no recent report, and conciergeReportID is undefined', async () => { From 703284c20318db5ba4acaacf62b0fb510dbfaf63 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 19 May 2026 10:38:41 +0430 Subject: [PATCH 16/18] guard search route helper before navigation is ready --- .../Navigation/helpers/isSearchTopmostFullScreenRoute.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts b/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts index 00631ae00e5e..9e03fe27e563 100644 --- a/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts +++ b/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts @@ -1,11 +1,15 @@ -import {navigationRef} from '@libs/Navigation/Navigation'; +import navigationRef from '@libs/Navigation/navigationRef'; import type {RootNavigatorParamList, State} from '@libs/Navigation/types'; import NAVIGATORS from '@src/NAVIGATORS'; import getActiveTabName from './getActiveTabName'; import {isFullScreenName} from './isNavigatorName'; const isSearchTopmostFullScreenRoute = (): boolean => { - const rootState = navigationRef.getRootState() as State; + if (!navigationRef.isReady()) { + return false; + } + + const rootState = navigationRef.getRootState?.() as State; if (!rootState) { return false; From a79ba6d0e8ca8377e71912c7e354eed5686d0e82 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 19 May 2026 10:46:21 +0430 Subject: [PATCH 17/18] fix chat expense preview navigation context --- .../inbox/report/actionContents/ChatTransactionPreview.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/inbox/report/actionContents/ChatTransactionPreview.tsx b/src/pages/inbox/report/actionContents/ChatTransactionPreview.tsx index ffde941139d1..dad4e01e7378 100644 --- a/src/pages/inbox/report/actionContents/ChatTransactionPreview.tsx +++ b/src/pages/inbox/report/actionContents/ChatTransactionPreview.tsx @@ -7,6 +7,7 @@ import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext'; import Navigation from '@libs/Navigation/Navigation'; import {getIOUReportIDFromReportActionPreview, isSplitBillAction, isTrackExpenseAction} from '@libs/ReportActionsUtils'; import {createTransactionThreadReport} from '@userActions/Report'; @@ -79,13 +80,13 @@ function ChatTransactionPreview({action, reportID, originalReportID, chatReportI iouReportAction: action, }); if (createdTransactionThreadReport?.reportID) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(createdTransactionThreadReport.reportID, undefined, undefined, Navigation.getActiveRoute())); + Navigation.navigate(getReportRouteForCurrentContext({reportID: createdTransactionThreadReport.reportID})); return; } return; } - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(action.childReportID, undefined, undefined, Navigation.getActiveRoute())); + Navigation.navigate(getReportRouteForCurrentContext({reportID: action.childReportID})); }} isTrackExpense={isTrackExpenseAction(action)} originalReportID={originalReportID} From 437f6249066da278ca34222f1805b1e13604f1f7 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 19 May 2026 11:00:30 +0430 Subject: [PATCH 18/18] fix navigationRef guard in search route helper --- src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts b/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts index 9e03fe27e563..67c80ec5496c 100644 --- a/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts +++ b/src/libs/Navigation/helpers/isSearchTopmostFullScreenRoute.ts @@ -5,7 +5,7 @@ import getActiveTabName from './getActiveTabName'; import {isFullScreenName} from './isNavigatorName'; const isSearchTopmostFullScreenRoute = (): boolean => { - if (!navigationRef.isReady()) { + if (!navigationRef.isReady?.()) { return false; }