From 9a9c7096a07da5503d157cd646c194b7c0c0b51d Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Fri, 13 Mar 2026 23:45:33 +0500
Subject: [PATCH 01/31] Add manual distance entry tab for editing distance
 requests

---
 src/CONST/index.ts                            |   1 +
 src/libs/ReportUtils.ts                       |   3 +-
 .../request/step/IOURequestStepDistance.tsx   | 272 ++++++++++++++----
 3 files changed, 226 insertions(+), 50 deletions(-)

diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index f2d66c6656c6..d80b3aa3d3f2 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -5984,6 +5984,7 @@ const CONST = {
         RECEIPT_TAB_ID: 'ReceiptTab',
         IOU_REQUEST_TYPE: 'iouRequestType',
         DISTANCE_REQUEST_TYPE: 'distanceRequestType',
+        DISTANCE_EDIT_TYPE: 'distanceEditType',
         SPLIT_EXPENSE_TAB_TYPE: 'splitExpenseTabType',
         SPLIT: {
             AMOUNT: 'amount',
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 96d8f512959e..6b2259028644 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -4655,7 +4655,6 @@ function getTransactionDetails(
     }
 
     const report = getReportOrDraftReport(transaction?.reportID, undefined, 'report' in transaction ? transaction.report : undefined);
-    const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction);
     const isFromExpenseReport = (!isEmptyObject(report) && isExpenseReport(report)) || isPaidGroupPolicyPolicyUtils(policy);
 
     return {
@@ -4681,7 +4680,7 @@ function getTransactionDetails(
         convertedAmount: getConvertedAmount(transaction, isFromExpenseReport, transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID, allowNegativeAmount, disableOppositeConversion),
         postedDate: getFormattedPostedDate(transaction),
         transactionID: transaction.transactionID,
-        ...(isManualDistanceRequest && {distance: transaction.comment?.customUnit?.quantity ?? undefined}),
+        ...(isDistanceRequest(transaction) && {distance: transaction.comment?.customUnit?.quantity ?? undefined}),
     };
 }
 
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 8e60839aaba5..025ee1dcab15 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -6,11 +6,18 @@ import {View} from 'react-native';
 import type {ScrollView as RNScrollView} from 'react-native';
 import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types';
 import type {OnyxEntry} from 'react-native-onyx';
+import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
 import Button from '@components/Button';
 import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter';
 import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem';
 import DotIndicatorMessage from '@components/DotIndicatorMessage';
 import DraggableList from '@components/DraggableList';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import NumberWithSymbolForm from '@components/NumberWithSymbolForm';
+import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
+import ScreenWrapper from '@components/ScreenWrapper';
+import TabSelector from '@components/TabSelector/TabSelector';
+import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
 import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
 import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
 import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy';
@@ -28,22 +35,26 @@ import useSelfDMReport from '@hooks/useSelfDMReport';
 import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWaypointItems from '@hooks/useWaypointItems';
-import {getIOURequestPolicyID, setMoneyRequestAmount, updateMoneyRequestDistance} from '@libs/actions/IOU';
+import {getIOURequestPolicyID, setMoneyRequestAmount, setMoneyRequestDistance, updateMoneyRequestDistance} from '@libs/actions/IOU';
 import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyRequest';
 import {setDraftSplitTransaction, setSplitShares} from '@libs/actions/IOU/Split';
 import {init, stop} from '@libs/actions/MapboxToken';
 import {openReport} from '@libs/actions/Report';
 import {openDraftDistanceExpense, removeWaypoint, updateWaypoints as updateWaypointsUtil} from '@libs/actions/Transaction';
 import {createBackupTransaction, removeBackupTransaction, restoreOriginalTransactionFromBackup} from '@libs/actions/TransactionEdit';
+import {canUseTouchScreen} from '@libs/DeviceCapabilities';
 import DistanceRequestUtils from '@libs/DistanceRequestUtils';
 import type {MileageRate} from '@libs/DistanceRequestUtils';
 import {getLatestErrorField} from '@libs/ErrorUtils';
 import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import {shouldUseTransactionDraft} from '@libs/IOUUtils';
 import Navigation from '@libs/Navigation/Navigation';
+import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator';
+import {roundToTwoDecimalPlaces} from '@libs/NumberUtils';
 import {getPolicy} from '@libs/PolicyUtils';
 import {isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtil} from '@libs/ReportUtils';
 import {getDistanceInMeters, getRateID, getRequestType, hasRoute, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
+import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import type {IOUType} from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -178,6 +189,37 @@ function IOURequestStepDistance({
 
     const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
 
+    // Defer rendering of OnyxTabNavigator by one frame to avoid interfering with the push animation.
+    // On second mount, the Onyx selected tab value is cached, causing OnyxTabNavigator
+    // to render its nested MaterialTopTabNavigator immediately. This interferes with
+    // the parent stack's push animation, making the screen appear instantly instead of sliding in.
+    const [isReadyForTabs, setIsReadyForTabs] = useState(false);
+    useEffect(() => {
+        const id = requestAnimationFrame(() => {
+            setIsReadyForTabs(true);
+        });
+        return () => cancelAnimationFrame(id);
+    }, []);
+
+    // Manual distance editing state
+    const manualNumberFormRef = useRef<NumberWithSymbolFormRef | null>(null);
+    const manualTextInputRef = useRef<BaseTextInputRef | null>(null);
+    const [manualFormError, setManualFormError] = useState<string>('');
+
+    const mileageRate = DistanceRequestUtils.getRate({
+        transaction,
+        policy,
+        useTransactionDistanceUnit: false,
+    });
+    const distanceUnit = mileageRate.unit;
+    const distanceRate = mileageRate.rate ?? 0;
+    const distanceInMeters = getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ? transaction.comment.customUnit.distanceUnit : distanceUnit);
+    const currentDistance = useMemo(
+        () =>
+            typeof transaction?.comment?.customUnit?.quantity === 'number' ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined,
+        [distanceInMeters, distanceUnit, transaction?.comment?.customUnit?.quantity],
+    );
+
     // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
     const setDistanceRequestData = useCallback(
         (participants: Participant[]) => {
@@ -192,16 +234,15 @@ function IOURequestStepDistance({
             const IOUpolicy = getPolicy(report?.policyID ?? IOUpolicyID);
             const policyCurrency = policy?.outputCurrency ?? personalPolicy?.outputCurrency ?? CONST.CURRENCY.USD;
 
-            const mileageRates = DistanceRequestUtils.getMileageRates(IOUpolicy);
+            const policyMileageRates = DistanceRequestUtils.getMileageRates(IOUpolicy);
             const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(IOUpolicy);
-            const mileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction)
+            const selectedMileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction)
                 ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction)
-                : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-                  (customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate;
+                : (customUnitRateID && policyMileageRates?.[customUnitRateID]) || defaultMileageRate;
 
-            const {unit, rate} = mileageRate ?? {};
+            const {unit, rate} = selectedMileageRate ?? {};
             const distance = getDistanceInMeters(transaction, unit);
-            const currency = mileageRate?.currency ?? policyCurrency;
+            const currency = selectedMileageRate?.currency ?? policyCurrency;
             const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0);
             setMoneyRequestAmount(transactionID, amount, currency);
 
@@ -525,6 +566,72 @@ function IOURequestStepDistance({
         parentReportNextStep,
     ]);
 
+    const submitManualDistance = useCallback(() => {
+        const value = manualNumberFormRef.current?.getNumber() ?? '';
+        if (!value.length || parseFloat(value) <= 0) {
+            setManualFormError(translate('iou.error.invalidDistance'));
+            return;
+        }
+        if (!DistanceRequestUtils.isDistanceAmountWithinLimit(parseFloat(value), distanceRate)) {
+            setManualFormError(translate('iou.error.distanceAmountTooLargeReduceDistance'));
+            return;
+        }
+
+        const distanceAsFloat = roundToTwoDecimalPlaces(parseFloat(value));
+        setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
+
+        if (isEditingSplit && transaction) {
+            setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
+            navigateBack();
+            return;
+        }
+
+        const transactionDistanceUnit = transaction?.comment?.customUnit?.distanceUnit;
+        const isDistanceChanged = currentDistance !== distanceAsFloat;
+        const isDistanceUnitChanged = transactionDistanceUnit && transactionDistanceUnit !== distanceUnit;
+
+        if (isDistanceChanged || isDistanceUnitChanged) {
+            updateMoneyRequestDistance({
+                transactionID: transaction?.transactionID,
+                transactionThreadReport: report,
+                parentReport,
+                distance: distanceAsFloat,
+                transactionBackup: undefined,
+                policy,
+                policyTagList: policyTags,
+                policyCategories,
+                currentUserAccountIDParam,
+                currentUserEmailParam,
+                isASAPSubmitBetaEnabled,
+                parentReportNextStep,
+                recentWaypoints,
+            });
+        }
+        transactionWasSaved.current = true;
+        navigateBack();
+    }, [
+        translate,
+        distanceRate,
+        transactionID,
+        action,
+        distanceUnit,
+        isEditingSplit,
+        transaction,
+        splitDraftTransaction,
+        policy,
+        navigateBack,
+        currentDistance,
+        report,
+        parentReport,
+        policyTags,
+        policyCategories,
+        currentUserAccountIDParam,
+        currentUserEmailParam,
+        isASAPSubmitBetaEnabled,
+        parentReportNextStep,
+        recentWaypoints,
+    ]);
+
     const renderItem = useCallback(
         ({item, drag, isActive, getIndex}: RenderItemParams<string>) => (
             <DistanceRequestRenderItem
@@ -540,54 +647,123 @@ function IOURequestStepDistance({
         [isLoadingRoute, navigateToWaypointEditPage, waypoints, getWaypointKey],
     );
 
+    const mapTabContent = (
+        <>
+            <View style={styles.flex1}>
+                <DraggableList
+                    data={waypointItems}
+                    keyExtractor={extractKey}
+                    onDragEnd={updateWaypoints}
+                    ref={scrollViewRef}
+                    renderItem={renderItem}
+                    ListFooterComponent={
+                        <DistanceRequestFooter
+                            waypoints={waypoints}
+                            navigateToWaypointEditPage={navigateToWaypointEditPage}
+                            transaction={transaction}
+                            policy={policy}
+                        />
+                    }
+                />
+            </View>
+            <View style={[styles.w100, styles.pt2]}>
+                {((shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError) && (
+                    <DotIndicatorMessage
+                        style={[styles.mh4, styles.mv3]}
+                        messages={getError()}
+                        type="error"
+                    />
+                )}
+                <Button
+                    success
+                    allowBubble
+                    pressOnEnter
+                    large
+                    style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
+                    onPress={submitWaypoints}
+                    text={buttonText}
+                    isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
+                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+                />
+            </View>
+        </>
+    );
+
+    const manualTabContent = (
+        <NumberWithSymbolForm
+            ref={manualTextInputRef}
+            numberFormRef={manualNumberFormRef}
+            value={currentDistance?.toString()}
+            onInputChange={() => {
+                if (!manualFormError) {
+                    return;
+                }
+                setManualFormError('');
+            }}
+            decimals={CONST.DISTANCE_DECIMAL_PLACES}
+            symbol={distanceUnit}
+            symbolPosition={CONST.TEXT_INPUT_SYMBOL_POSITION.SUFFIX}
+            isSymbolPressable={false}
+            symbolTextStyle={styles.textSupporting}
+            style={styles.iouAmountTextInput}
+            containerStyle={styles.iouAmountTextInputContainer}
+            autoGrowExtraSpace={variables.w80}
+            touchableInputWrapperStyle={styles.heightUndefined}
+            errorText={manualFormError}
+            accessibilityLabel={`${translate('common.distance')} (${translate(`common.${distanceUnit}`)})`}
+            footer={
+                <Button
+                    success
+                    allowBubble={false}
+                    pressOnEnter
+                    large
+                    style={[styles.w100, styles.mt5]}
+                    onPress={submitManualDistance}
+                    text={translate('common.save')}
+                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+                />
+            }
+        />
+    );
+
+    if (isEditing) {
+        const hasRequiredData = !!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage;
+        return (
+            <ScreenWrapper
+                shouldEnableMaxHeight={canUseTouchScreen()}
+                testID="IOURequestStepDistance"
+            >
+                <FullPageNotFoundView shouldShow={!hasRequiredData}>
+                    <HeaderWithBackButton
+                        title={translate('common.distance')}
+                        onBackButtonPress={navigateBack}
+                    />
+                    {isReadyForTabs ? (
+                        <OnyxTabNavigator
+                            id={CONST.TAB.DISTANCE_EDIT_TYPE}
+                            defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
+                            tabBar={TabSelector}
+                        >
+                            <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{() => <TabScreenWithFocusTrapWrapper>{mapTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
+                            <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{() => <TabScreenWithFocusTrapWrapper>{manualTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
+                        </OnyxTabNavigator>
+                    ) : (
+                        mapTabContent
+                    )}
+                </FullPageNotFoundView>
+            </ScreenWrapper>
+        );
+    }
+
     return (
         <StepScreenWrapper
             headerTitle={translate('common.distance')}
             onBackButtonPress={navigateBack}
             testID="IOURequestStepDistance"
-            shouldShowNotFoundPage={(isEditing && !currentTransaction?.comment?.waypoints) || shouldShowNotFoundPage}
+            shouldShowNotFoundPage={shouldShowNotFoundPage}
             shouldShowWrapper={!isCreatingNewRequest}
         >
-            <>
-                <View style={styles.flex1}>
-                    <DraggableList
-                        data={waypointItems}
-                        keyExtractor={extractKey}
-                        onDragEnd={updateWaypoints}
-                        ref={scrollViewRef}
-                        renderItem={renderItem}
-                        ListFooterComponent={
-                            <DistanceRequestFooter
-                                waypoints={waypoints}
-                                navigateToWaypointEditPage={navigateToWaypointEditPage}
-                                transaction={transaction}
-                                policy={policy}
-                            />
-                        }
-                    />
-                </View>
-                <View style={[styles.w100, styles.pt2]}>
-                    {/* Show error message if there is route error or there are less than 2 routes and user has tried submitting, */}
-                    {((shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError) && (
-                        <DotIndicatorMessage
-                            style={[styles.mh4, styles.mv3]}
-                            messages={getError()}
-                            type="error"
-                        />
-                    )}
-                    <Button
-                        success
-                        allowBubble
-                        pressOnEnter
-                        large
-                        style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
-                        onPress={submitWaypoints}
-                        text={buttonText}
-                        isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
-                        sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
-                    />
-                </View>
-            </>
+            {mapTabContent}
         </StepScreenWrapper>
     );
 }

From dc4e705a652c9aa2d0baa0aeb643561418099b04 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Tue, 17 Mar 2026 05:54:54 +0500
Subject: [PATCH 02/31] Remove deferred rendering workaround for
 OnyxTabNavigator in distance request step

---
 .../request/step/IOURequestStepDistance.tsx   | 32 +++++--------------
 1 file changed, 8 insertions(+), 24 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 025ee1dcab15..ede0c0c8989e 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -189,18 +189,6 @@ function IOURequestStepDistance({
 
     const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
 
-    // Defer rendering of OnyxTabNavigator by one frame to avoid interfering with the push animation.
-    // On second mount, the Onyx selected tab value is cached, causing OnyxTabNavigator
-    // to render its nested MaterialTopTabNavigator immediately. This interferes with
-    // the parent stack's push animation, making the screen appear instantly instead of sliding in.
-    const [isReadyForTabs, setIsReadyForTabs] = useState(false);
-    useEffect(() => {
-        const id = requestAnimationFrame(() => {
-            setIsReadyForTabs(true);
-        });
-        return () => cancelAnimationFrame(id);
-    }, []);
-
     // Manual distance editing state
     const manualNumberFormRef = useRef<NumberWithSymbolFormRef | null>(null);
     const manualTextInputRef = useRef<BaseTextInputRef | null>(null);
@@ -738,18 +726,14 @@ function IOURequestStepDistance({
                         title={translate('common.distance')}
                         onBackButtonPress={navigateBack}
                     />
-                    {isReadyForTabs ? (
-                        <OnyxTabNavigator
-                            id={CONST.TAB.DISTANCE_EDIT_TYPE}
-                            defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
-                            tabBar={TabSelector}
-                        >
-                            <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{() => <TabScreenWithFocusTrapWrapper>{mapTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
-                            <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{() => <TabScreenWithFocusTrapWrapper>{manualTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
-                        </OnyxTabNavigator>
-                    ) : (
-                        mapTabContent
-                    )}
+                    <OnyxTabNavigator
+                        id={CONST.TAB.DISTANCE_EDIT_TYPE}
+                        defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
+                        tabBar={TabSelector}
+                    >
+                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{() => <TabScreenWithFocusTrapWrapper>{mapTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
+                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{() => <TabScreenWithFocusTrapWrapper>{manualTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
+                    </OnyxTabNavigator>
                 </FullPageNotFoundView>
             </ScreenWrapper>
         );

From ca02638031c7e1f6ec88558ad5e3ec5677cd683b Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Tue, 17 Mar 2026 06:08:20 +0500
Subject: [PATCH 03/31] Fix distance request step to use currentTransaction and
 handle updates correctly

---
 .../request/step/IOURequestStepDistance.tsx   | 50 +++++++++++--------
 1 file changed, 28 insertions(+), 22 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index ede0c0c8989e..f04057d653d5 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -195,17 +195,17 @@ function IOURequestStepDistance({
     const [manualFormError, setManualFormError] = useState<string>('');
 
     const mileageRate = DistanceRequestUtils.getRate({
-        transaction,
+        transaction: currentTransaction,
         policy,
         useTransactionDistanceUnit: false,
     });
     const distanceUnit = mileageRate.unit;
     const distanceRate = mileageRate.rate ?? 0;
-    const distanceInMeters = getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ? transaction.comment.customUnit.distanceUnit : distanceUnit);
+    const distanceInMeters = getDistanceInMeters(currentTransaction, currentTransaction?.comment?.customUnit?.distanceUnit ? currentTransaction.comment.customUnit.distanceUnit : distanceUnit);
     const currentDistance = useMemo(
         () =>
-            typeof transaction?.comment?.customUnit?.quantity === 'number' ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined,
-        [distanceInMeters, distanceUnit, transaction?.comment?.customUnit?.quantity],
+            typeof currentTransaction?.comment?.customUnit?.quantity === 'number' ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined,
+        [distanceInMeters, distanceUnit, currentTransaction?.comment?.customUnit?.quantity],
     );
 
     // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
@@ -566,35 +566,39 @@ function IOURequestStepDistance({
         }
 
         const distanceAsFloat = roundToTwoDecimalPlaces(parseFloat(value));
-        setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
 
         if (isEditingSplit && transaction) {
+            setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
             setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
             navigateBack();
             return;
         }
 
-        const transactionDistanceUnit = transaction?.comment?.customUnit?.distanceUnit;
+        const transactionDistanceUnit = currentTransaction?.comment?.customUnit?.distanceUnit;
         const isDistanceChanged = currentDistance !== distanceAsFloat;
         const isDistanceUnitChanged = transactionDistanceUnit && transactionDistanceUnit !== distanceUnit;
 
-        if (isDistanceChanged || isDistanceUnitChanged) {
-            updateMoneyRequestDistance({
-                transactionID: transaction?.transactionID,
-                transactionThreadReport: report,
-                parentReport,
-                distance: distanceAsFloat,
-                transactionBackup: undefined,
-                policy,
-                policyTagList: policyTags,
-                policyCategories,
-                currentUserAccountIDParam,
-                currentUserEmailParam,
-                isASAPSubmitBetaEnabled,
-                parentReportNextStep,
-                recentWaypoints,
-            });
+        if (!isDistanceChanged && !isDistanceUnitChanged) {
+            navigateBack();
+            return;
         }
+
+        setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
+        updateMoneyRequestDistance({
+            transactionID: transaction?.transactionID,
+            transactionThreadReport: report,
+            parentReport,
+            distance: distanceAsFloat,
+            transactionBackup,
+            policy,
+            policyTagList: policyTags,
+            policyCategories,
+            currentUserAccountIDParam,
+            currentUserEmailParam,
+            isASAPSubmitBetaEnabled,
+            parentReportNextStep,
+            recentWaypoints,
+        });
         transactionWasSaved.current = true;
         navigateBack();
     }, [
@@ -605,10 +609,12 @@ function IOURequestStepDistance({
         distanceUnit,
         isEditingSplit,
         transaction,
+        currentTransaction?.comment?.customUnit?.distanceUnit,
         splitDraftTransaction,
         policy,
         navigateBack,
         currentDistance,
+        transactionBackup,
         report,
         parentReport,
         policyTags,

From d4c2910b50025d7ed048a8f793c922d2dacf813e Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Tue, 17 Mar 2026 07:43:35 +0500
Subject: [PATCH 04/31] Fix distance request to properly sync manual input with
 route changes and handle navigation

---
 src/libs/TransactionUtils/index.ts            |  1 +
 .../request/step/IOURequestStepDistance.tsx   | 22 ++++++++++++++-----
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index f5712fd33755..f78a91c40b28 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -861,6 +861,7 @@ function getUpdatedTransaction({
         const distance = roundToTwoDecimalPlaces(transactionChanges.distance ?? 0);
 
         lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance);
+        lodashSet(updatedTransaction, 'routes.route0.distance', null);
         shouldStopSmartscan = true;
 
         const updatedMileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, useTransactionDistanceUnit: false});
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index f04057d653d5..1784c51afc56 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -203,11 +203,16 @@ function IOURequestStepDistance({
     const distanceRate = mileageRate.rate ?? 0;
     const distanceInMeters = getDistanceInMeters(currentTransaction, currentTransaction?.comment?.customUnit?.distanceUnit ? currentTransaction.comment.customUnit.distanceUnit : distanceUnit);
     const currentDistance = useMemo(
-        () =>
-            typeof currentTransaction?.comment?.customUnit?.quantity === 'number' ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined,
-        [distanceInMeters, distanceUnit, currentTransaction?.comment?.customUnit?.quantity],
+        () => (distanceInMeters > 0 ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined),
+        [distanceInMeters, distanceUnit],
     );
 
+    // Keep the manual tab's NumberWithSymbolForm in sync when the distance changes
+    // externally (e.g. route recalculated after waypoint edits on the map tab).
+    useEffect(() => {
+        manualNumberFormRef.current?.updateNumber(currentDistance?.toString() ?? '');
+    }, [currentDistance]);
+
     // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
     const setDistanceRequestData = useCallback(
         (participants: Participant[]) => {
@@ -320,8 +325,13 @@ function IOURequestStepDistance({
     }, []);
 
     const navigateBack = useCallback(() => {
-        Navigation.goBack(backTo);
-    }, [backTo]);
+        // When editing, the OnyxTabNavigator's backBehavior="initialRoute" causes
+        // goBack() without a route to first navigate within the tab navigator
+        // (e.g. manual tab → map tab) instead of exiting the screen.
+        // Provide an explicit fallback route to bypass the tab navigator.
+        const fallbackRoute = backTo ?? (isEditing ? ROUTES.REPORT_WITH_ID.getRoute(report?.reportID ?? reportID) : undefined);
+        Navigation.goBack(fallbackRoute);
+    }, [backTo, isEditing, report?.reportID, reportID]);
 
     /**
      * Takes the user to the page for editing a specific waypoint
@@ -497,6 +507,7 @@ function IOURequestStepDistance({
             const addresses = Object.fromEntries(Object.entries(waypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
             const hasRouteChanged = !deepEqual(transactionBackup?.routes, transaction?.routes);
             if (deepEqual(oldAddresses, addresses)) {
+                transactionWasSaved.current = true;
                 navigateBack();
                 return;
             }
@@ -579,6 +590,7 @@ function IOURequestStepDistance({
         const isDistanceUnitChanged = transactionDistanceUnit && transactionDistanceUnit !== distanceUnit;
 
         if (!isDistanceChanged && !isDistanceUnitChanged) {
+            transactionWasSaved.current = true;
             navigateBack();
             return;
         }

From 7bd07258647a162fe9101ffb266e7d5fa5c3a84f Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Tue, 17 Mar 2026 08:06:12 +0500
Subject: [PATCH 05/31] fixed prettier

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 1784c51afc56..7ffd4150e933 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -201,7 +201,10 @@ function IOURequestStepDistance({
     });
     const distanceUnit = mileageRate.unit;
     const distanceRate = mileageRate.rate ?? 0;
-    const distanceInMeters = getDistanceInMeters(currentTransaction, currentTransaction?.comment?.customUnit?.distanceUnit ? currentTransaction.comment.customUnit.distanceUnit : distanceUnit);
+    const distanceInMeters = getDistanceInMeters(
+        currentTransaction,
+        currentTransaction?.comment?.customUnit?.distanceUnit ? currentTransaction.comment.customUnit.distanceUnit : distanceUnit,
+    );
     const currentDistance = useMemo(
         () => (distanceInMeters > 0 ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined),
         [distanceInMeters, distanceUnit],

From d1690b8764182f966a003514f818315f7f1bc253 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Sun, 22 Mar 2026 12:16:00 +0500
Subject: [PATCH 06/31] Add waypoints to IOURequestStepDistance dependencies

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 7ffd4150e933..eccad455aa43 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -603,6 +603,7 @@ function IOURequestStepDistance({
             transactionID: transaction?.transactionID,
             transactionThreadReport: report,
             parentReport,
+            waypoints,
             distance: distanceAsFloat,
             transactionBackup,
             policy,
@@ -629,6 +630,7 @@ function IOURequestStepDistance({
         policy,
         navigateBack,
         currentDistance,
+        waypoints,
         transactionBackup,
         report,
         parentReport,

From 1a4c49d47a23dc0637039836d342ecf3c5853d6d Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Thu, 26 Mar 2026 07:23:10 +0500
Subject: [PATCH 07/31] Preserve manually entered distance when reopening
 distance editor

---
 .../request/step/IOURequestStepDistance.tsx   | 30 +++++++++++--------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index eccad455aa43..db79c80f1291 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -201,20 +201,18 @@ function IOURequestStepDistance({
     });
     const distanceUnit = mileageRate.unit;
     const distanceRate = mileageRate.rate ?? 0;
-    const distanceInMeters = getDistanceInMeters(
-        currentTransaction,
-        currentTransaction?.comment?.customUnit?.distanceUnit ? currentTransaction.comment.customUnit.distanceUnit : distanceUnit,
-    );
+    const currentTransactionDistanceUnit = currentTransaction?.comment?.customUnit?.distanceUnit;
+    const distanceInMeters = getDistanceInMeters(currentTransaction, currentTransactionDistanceUnit ?? distanceUnit);
     const currentDistance = useMemo(
         () => (distanceInMeters > 0 ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)) : undefined),
         [distanceInMeters, distanceUnit],
     );
 
-    // Keep the manual tab's NumberWithSymbolForm in sync when the distance changes
-    // externally (e.g. route recalculated after waypoint edits on the map tab).
-    useEffect(() => {
-        manualNumberFormRef.current?.updateNumber(currentDistance?.toString() ?? '');
-    }, [currentDistance]);
+    // For the manual distance input, prefer quantity (the user's manually edited value) over
+    // routes.route0.distance (the route-calculated value). This ensures that after a manual edit,
+    // re-opening the distance editor shows the manually entered value, not the route distance.
+    const quantity = currentTransaction?.comment?.customUnit?.quantity;
+    const manualInputDistance = useMemo(() => (quantity ? roundToTwoDecimalPlaces(quantity) : currentDistance), [quantity, currentDistance]);
 
     // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
     const setDistanceRequestData = useCallback(
@@ -515,6 +513,11 @@ function IOURequestStepDistance({
                 return;
             }
             if (transaction?.transactionID && report?.reportID) {
+                // When waypoints change, include the route-calculated distance so the server
+                // updates quantity to match the new route instead of keeping the old value.
+                const routeDistanceInMeters = transaction?.routes?.route0?.distance;
+                const routeDistanceInUnit = routeDistanceInMeters ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit)) : undefined;
+
                 updateMoneyRequestDistance({
                     transactionID: transaction?.transactionID,
                     transactionThreadReport: report,
@@ -522,6 +525,7 @@ function IOURequestStepDistance({
                     waypoints,
                     recentWaypoints,
                     ...(hasRouteChanged ? {routes: transaction?.routes} : {}),
+                    ...(routeDistanceInUnit ? {distance: routeDistanceInUnit} : {}),
                     policy,
                     policyTagList: policyTags,
                     policyCategories,
@@ -566,6 +570,7 @@ function IOURequestStepDistance({
         currentUserEmailParam,
         isASAPSubmitBetaEnabled,
         parentReportNextStep,
+        distanceUnit,
     ]);
 
     const submitManualDistance = useCallback(() => {
@@ -589,7 +594,7 @@ function IOURequestStepDistance({
         }
 
         const transactionDistanceUnit = currentTransaction?.comment?.customUnit?.distanceUnit;
-        const isDistanceChanged = currentDistance !== distanceAsFloat;
+        const isDistanceChanged = manualInputDistance !== distanceAsFloat;
         const isDistanceUnitChanged = transactionDistanceUnit && transactionDistanceUnit !== distanceUnit;
 
         if (!isDistanceChanged && !isDistanceUnitChanged) {
@@ -629,7 +634,7 @@ function IOURequestStepDistance({
         splitDraftTransaction,
         policy,
         navigateBack,
-        currentDistance,
+        manualInputDistance,
         waypoints,
         transactionBackup,
         report,
@@ -702,9 +707,10 @@ function IOURequestStepDistance({
 
     const manualTabContent = (
         <NumberWithSymbolForm
+            key={manualInputDistance}
             ref={manualTextInputRef}
             numberFormRef={manualNumberFormRef}
-            value={currentDistance?.toString()}
+            value={manualInputDistance?.toString()}
             onInputChange={() => {
                 if (!manualFormError) {
                     return;

From 112566623d74ae2cdd8583071faf43533edb72f5 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Thu, 26 Mar 2026 07:38:54 +0500
Subject: [PATCH 08/31] Add mock for material-top-tabs navigator in
 IOURequestStepDistanceTest

---
 tests/ui/IOURequestStepDistanceTest.tsx | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index bc3228e5e050..c2c84158b9c6 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -85,6 +85,16 @@ jest.mock('@libs/actions/TransactionEdit', () => ({
 jest.mock('@components/ProductTrainingContext', () => ({
     useProductTrainingContext: () => [false],
 }));
+
+jest.mock('@react-navigation/material-top-tabs', () => {
+    const React2 = require('react');
+    return {
+        createMaterialTopTabNavigator: () => ({
+            Navigator: ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children),
+            Screen: ({children}: {children: () => React.ReactNode}) => React2.createElement(React2.Fragment, null, typeof children === 'function' ? children() : children),
+        }),
+    };
+});
 jest.mock('@src/hooks/useResponsiveLayout');
 
 jest.mock('@libs/Navigation/navigationRef', () => ({

From 5d8bcc8bde18c7c1509acbec55207cd19de85fd7 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Thu, 26 Mar 2026 07:45:27 +0500
Subject: [PATCH 09/31] Fix distance editor not saving when waypoints changed
 but distance remained same

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index d181b9ca4b20..4b8917344caa 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -591,7 +591,7 @@ function IOURequestStepDistance({
 
         if (isEditingSplit && transaction) {
             setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
-            setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
+            setDraftSplitTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
             navigateBack();
             return;
         }
@@ -600,7 +600,14 @@ function IOURequestStepDistance({
         const isDistanceChanged = manualInputDistance !== distanceAsFloat;
         const isDistanceUnitChanged = transactionDistanceUnit && transactionDistanceUnit !== distanceUnit;
 
-        if (!isDistanceChanged && !isDistanceUnitChanged) {
+        // Check if waypoints were edited on the map tab before the user switched to manual.
+        // If so, we must still send the update even if the distance value itself didn't change.
+        const oldWaypoints = transactionBackup?.comment?.waypoints ?? {};
+        const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
+        const currentAddresses = Object.fromEntries(Object.entries(waypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
+        const haveWaypointsChanged = !deepEqual(oldAddresses, currentAddresses);
+
+        if (!isDistanceChanged && !isDistanceUnitChanged && !haveWaypointsChanged) {
             transactionWasSaved.current = true;
             navigateBack();
             return;

From fbae72e748f97340c3c1c61e624f447184e9f244 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Thu, 26 Mar 2026 10:21:32 +0500
Subject: [PATCH 10/31] Update mock to use OnyxTabNavigator instead of
 material-top-tabs

---
 tests/ui/IOURequestStepDistanceTest.tsx | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index c2c84158b9c6..e2e5dd18fd16 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -86,13 +86,19 @@ jest.mock('@components/ProductTrainingContext', () => ({
     useProductTrainingContext: () => [false],
 }));
 
-jest.mock('@react-navigation/material-top-tabs', () => {
+jest.mock('@libs/Navigation/OnyxTabNavigator', () => {
     const React2 = require('react');
+    const OnyxTabNavigator = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
+    const TopTab = {
+        Screen: ({children}: {children: () => React.ReactNode}) => React2.createElement(React2.Fragment, null, typeof children === 'function' ? children() : children),
+    };
+    const TabScreenWithFocusTrapWrapper = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
     return {
-        createMaterialTopTabNavigator: () => ({
-            Navigator: ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children),
-            Screen: ({children}: {children: () => React.ReactNode}) => React2.createElement(React2.Fragment, null, typeof children === 'function' ? children() : children),
-        }),
+        // eslint-disable-next-line @typescript-eslint/naming-convention
+        __esModule: true,
+        default: OnyxTabNavigator,
+        TopTab,
+        TabScreenWithFocusTrapWrapper,
     };
 });
 jest.mock('@src/hooks/useResponsiveLayout');

From 9b40d725907081b02043d0f983aca4017b3215b3 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Thu, 26 Mar 2026 10:36:47 +0500
Subject: [PATCH 11/31] Add OnyxTabNavigator mock to IOURequestStepDistanceTest

---
 .../components/IOURequestStepDistanceTest.tsx | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/tests/ui/components/IOURequestStepDistanceTest.tsx b/tests/ui/components/IOURequestStepDistanceTest.tsx
index 4b4edaa0c573..2586381a911f 100644
--- a/tests/ui/components/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/components/IOURequestStepDistanceTest.tsx
@@ -56,6 +56,27 @@ jest.mock('@libs/EmojiTrie', () => ({
 jest.mock('@components/ProductTrainingContext', () => ({
     useProductTrainingContext: () => [false],
 }));
+
+jest.mock('@libs/Navigation/OnyxTabNavigator', () => {
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+    const React2 = require('react');
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
+    const OnyxTabNavigator = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
+    const TopTab = {
+        // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
+        Screen: ({children}: {children: () => React.ReactNode}) => React2.createElement(React2.Fragment, null, typeof children === 'function' ? children() : children),
+    };
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
+    const TabScreenWithFocusTrapWrapper = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
+    return {
+        // eslint-disable-next-line @typescript-eslint/naming-convention
+        __esModule: true,
+        default: OnyxTabNavigator,
+        TopTab,
+        TabScreenWithFocusTrapWrapper,
+    };
+});
+
 jest.mock('@src/hooks/useResponsiveLayout');
 
 jest.mock('@libs/Navigation/navigationRef', () => ({

From b05e0e213d74ba246e5a95934eef3c5909cb853c Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Tue, 31 Mar 2026 08:02:21 +0500
Subject: [PATCH 12/31] Refactor distance request to prioritize quantity over
 route distance and extract tab content components

---
 .../TransactionUtils/getDistanceInMeters.ts   |  15 +-
 src/libs/TransactionUtils/index.ts            |   4 +-
 src/libs/actions/IOU/index.ts                 |   9 +-
 .../request/step/DistanceManualTabContent.tsx |  61 +++++
 .../request/step/DistanceMapTabContent.tsx    | 110 +++++++++
 .../request/step/IOURequestStepDistance.tsx   | 210 ++++++++++--------
 6 files changed, 304 insertions(+), 105 deletions(-)
 create mode 100644 src/pages/iou/request/step/DistanceManualTabContent.tsx
 create mode 100644 src/pages/iou/request/step/DistanceMapTabContent.tsx

diff --git a/src/libs/TransactionUtils/getDistanceInMeters.ts b/src/libs/TransactionUtils/getDistanceInMeters.ts
index 0efe72759d5d..5afcdb5da79c 100644
--- a/src/libs/TransactionUtils/getDistanceInMeters.ts
+++ b/src/libs/TransactionUtils/getDistanceInMeters.ts
@@ -5,16 +5,19 @@ import type {Unit} from '@src/types/onyx/Policy';
 // Get the distance in meters from the transaction.
 // This function is placed in a separate file to avoid circular dependencies.
 function getDistanceInMeters(transaction: OnyxInputOrEntry<Transaction>, unit: Unit | undefined) {
-    // If we are creating a new distance request, the distance is available in routes.route0.distance and it's already in meters.
+    // When quantity is set (manually edited or saved by the server), it is the authoritative distance.
+    // It takes priority over routes.route0.distance which may still hold a stale route-calculated
+    // value after a manual edit.
+    if (transaction?.comment?.customUnit?.quantity && unit) {
+        return DistanceRequestUtils.convertToDistanceInMeters(transaction.comment.customUnit.quantity, unit);
+    }
+
+    // During the creation flow (before the transaction is saved), quantity is not yet set.
+    // The distance comes from the route calculation in routes.route0.distance (already in meters).
     if (transaction?.routes?.route0?.distance) {
         return transaction.routes.route0.distance;
     }
 
-    // If the request is completed, transaction.routes is cleared and comment.customUnit.quantity holds the new distance in the selected unit.
-    // We need to convert it from the selected distance unit to meters.
-    if (transaction?.comment?.customUnit?.quantity && unit) {
-        return DistanceRequestUtils.convertToDistanceInMeters(transaction.comment.customUnit.quantity, unit);
-    }
     return 0;
 }
 
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 246300e8ca69..f5cec1f72a11 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -727,7 +727,9 @@ function getUpdatedTransaction({
             const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy});
             const {unit, rate} = mileageRate;
 
-            const distanceInMeters = getDistanceInMeters(transaction, unit);
+            // Use route distance directly since waypoints changed and the route was recalculated.
+            // getDistanceInMeters prefers quantity which may hold a stale manually-edited value.
+            const distanceInMeters = transactionChanges.routes?.route0?.distance ?? getDistanceInMeters(transaction, unit);
             const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0);
             const updatedAmount = isFromExpenseReport || isUnReportedExpense ? -amount : amount;
             const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(
diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts
index bd9a5020298e..18000cbf29f9 100644
--- a/src/libs/actions/IOU/index.ts
+++ b/src/libs/actions/IOU/index.ts
@@ -4169,6 +4169,9 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     // For split transactions, the merchant and amount are already computed in transactionChanges,
     // so we can build a valid optimistic MODIFIED_EXPENSE even when waypoints are pending.
     const hasSplitDistanceMessageFields = !!isSplitTransaction && hasModifiedMerchant && hasModifiedAmount;
+    // When distance is provided alongside waypoints (route was already calculated), we have valid
+    // merchant/amount data to build the optimistic report action instead of waiting for the server.
+    const hasDistanceWithWaypoints = hasPendingWaypoints && 'distance' in transactionChanges;
     if (transaction && updatedTransaction && (hasPendingWaypoints || hasModifiedDistanceRate)) {
         // Delete the draft transaction when editing waypoints when the server responds successfully and there are no errors
         successData.push({
@@ -4204,7 +4207,11 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     const updatedReportAction = shouldBuildOptimisticModifiedExpenseReportAction
         ? buildOptimisticModifiedExpenseReportAction(transactionThreadReport, transaction, transactionChanges, isFromExpenseReport, policy, updatedTransaction, allowNegative)
         : null;
-    if ((!hasPendingWaypoints || hasSplitDistanceMessageFields) && !(hasModifiedDistanceRate && isFetchingWaypointsFromServer(transaction)) && updatedReportAction) {
+    if (
+        (!hasPendingWaypoints || hasSplitDistanceMessageFields || hasDistanceWithWaypoints) &&
+        !(hasModifiedDistanceRate && isFetchingWaypointsFromServer(transaction)) &&
+        updatedReportAction
+    ) {
         apiParams.reportActionID = updatedReportAction.reportActionID;
 
         optimisticData.push({
diff --git a/src/pages/iou/request/step/DistanceManualTabContent.tsx b/src/pages/iou/request/step/DistanceManualTabContent.tsx
new file mode 100644
index 000000000000..5014757be3e8
--- /dev/null
+++ b/src/pages/iou/request/step/DistanceManualTabContent.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import Button from '@components/Button';
+import NumberWithSymbolForm from '@components/NumberWithSymbolForm';
+import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
+import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import type {Unit} from '@src/types/onyx/Policy';
+
+type DistanceManualTabContentProps = {
+    currentDistance: number | undefined;
+    distanceUnit: Unit;
+    onSubmit: () => void;
+    manualFormError: string;
+    onInputChange: () => void;
+    manualTextInputRef: React.RefObject<BaseTextInputRef | null>;
+    manualNumberFormRef: React.RefObject<NumberWithSymbolFormRef | null>;
+};
+
+function DistanceManualTabContent({currentDistance, distanceUnit, onSubmit, manualFormError, onInputChange, manualTextInputRef, manualNumberFormRef}: DistanceManualTabContentProps) {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+
+    return (
+        <NumberWithSymbolForm
+            ref={manualTextInputRef}
+            numberFormRef={manualNumberFormRef}
+            value={currentDistance?.toString()}
+            onInputChange={onInputChange}
+            decimals={CONST.DISTANCE_DECIMAL_PLACES}
+            symbol={distanceUnit}
+            symbolPosition={CONST.TEXT_INPUT_SYMBOL_POSITION.SUFFIX}
+            isSymbolPressable={false}
+            symbolTextStyle={styles.textSupporting}
+            style={styles.iouAmountTextInput}
+            containerStyle={styles.iouAmountTextInputContainer}
+            autoGrowExtraSpace={variables.w80}
+            touchableInputWrapperStyle={styles.heightUndefined}
+            errorText={manualFormError}
+            accessibilityLabel={`${translate('common.distance')} (${translate(`common.${distanceUnit}`)})`}
+            footer={
+                <Button
+                    success
+                    allowBubble={false}
+                    pressOnEnter
+                    large
+                    style={[styles.w100, styles.mt5]}
+                    onPress={onSubmit}
+                    text={translate('common.save')}
+                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+                />
+            }
+        />
+    );
+}
+
+DistanceManualTabContent.displayName = 'DistanceManualTabContent';
+
+export default DistanceManualTabContent;
diff --git a/src/pages/iou/request/step/DistanceMapTabContent.tsx b/src/pages/iou/request/step/DistanceMapTabContent.tsx
new file mode 100644
index 000000000000..5294479fa4e9
--- /dev/null
+++ b/src/pages/iou/request/step/DistanceMapTabContent.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+import {View} from 'react-native';
+// eslint-disable-next-line no-restricted-imports
+import type {ScrollView as RNScrollView} from 'react-native';
+import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types';
+import type {OnyxEntry} from 'react-native-onyx';
+import Button from '@components/Button';
+import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter';
+import DotIndicatorMessage from '@components/DotIndicatorMessage';
+import DraggableList from '@components/DraggableList';
+import useThemeStyles from '@hooks/useThemeStyles';
+import CONST from '@src/CONST';
+import type {Policy} from '@src/types/onyx';
+import type {Errors} from '@src/types/onyx/OnyxCommon';
+import type Transaction from '@src/types/onyx/Transaction';
+import type {WaypointCollection} from '@src/types/onyx/Transaction';
+
+type DistanceMapTabContentProps = {
+    waypointItems: string[];
+    waypoints: WaypointCollection;
+    extractKey: (key: string) => string;
+    updateWaypoints: (data: {data: string[]}) => void;
+    scrollViewRef: React.RefObject<RNScrollView | null>;
+    renderItem: (params: RenderItemParams<string>) => React.JSX.Element;
+    navigateToWaypointEditPage: (index: number) => void;
+    transaction: OnyxEntry<Transaction>;
+    policy: OnyxEntry<Policy>;
+    shouldShowAtLeastTwoDifferentWaypointsError: boolean;
+    atLeastTwoDifferentWaypointsError: boolean;
+    duplicateWaypointsError: boolean;
+    hasRouteError: boolean;
+    getError: () => Errors;
+    submitWaypoints: () => void;
+    buttonText: string;
+    isOffline: boolean;
+    isLoadingRoute: boolean;
+    shouldFetchRoute: boolean;
+    isLoading: boolean;
+};
+
+function DistanceMapTabContent({
+    waypointItems,
+    waypoints,
+    extractKey,
+    updateWaypoints,
+    scrollViewRef,
+    renderItem,
+    navigateToWaypointEditPage,
+    transaction,
+    policy,
+    shouldShowAtLeastTwoDifferentWaypointsError,
+    atLeastTwoDifferentWaypointsError,
+    duplicateWaypointsError,
+    hasRouteError,
+    getError,
+    submitWaypoints,
+    buttonText,
+    isOffline,
+    isLoadingRoute,
+    shouldFetchRoute,
+    isLoading,
+}: DistanceMapTabContentProps) {
+    const styles = useThemeStyles();
+
+    return (
+        <>
+            <View style={styles.flex1}>
+                <DraggableList
+                    data={waypointItems}
+                    keyExtractor={extractKey}
+                    onDragEnd={updateWaypoints}
+                    ref={scrollViewRef}
+                    renderItem={renderItem}
+                    ListFooterComponent={
+                        <DistanceRequestFooter
+                            waypoints={waypoints}
+                            navigateToWaypointEditPage={navigateToWaypointEditPage}
+                            transaction={transaction}
+                            policy={policy}
+                        />
+                    }
+                />
+            </View>
+            <View style={[styles.w100, styles.pt2]}>
+                {((shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError) && (
+                    <DotIndicatorMessage
+                        style={[styles.mh4, styles.mv3]}
+                        messages={getError()}
+                        type="error"
+                    />
+                )}
+                <Button
+                    success
+                    allowBubble
+                    pressOnEnter
+                    large
+                    style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
+                    onPress={submitWaypoints}
+                    text={buttonText}
+                    isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
+                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+                />
+            </View>
+        </>
+    );
+}
+
+DistanceMapTabContent.displayName = 'DistanceMapTabContent';
+
+export default DistanceMapTabContent;
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index d7fedecd5a0e..55b67a9a3027 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -1,19 +1,13 @@
 import {deepEqual} from 'fast-equals';
 import isEmpty from 'lodash/isEmpty';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
 // eslint-disable-next-line no-restricted-imports
 import type {ScrollView as RNScrollView} from 'react-native';
 import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types';
 import type {OnyxEntry} from 'react-native-onyx';
 import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import Button from '@components/Button';
-import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter';
 import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem';
-import DotIndicatorMessage from '@components/DotIndicatorMessage';
-import DraggableList from '@components/DraggableList';
 import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import NumberWithSymbolForm from '@components/NumberWithSymbolForm';
 import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
 import ScreenWrapper from '@components/ScreenWrapper';
 import TabSelector from '@components/TabSelector/TabSelector';
@@ -33,7 +27,6 @@ import usePrevious from '@hooks/usePrevious';
 import useReportAttributes from '@hooks/useReportAttributes';
 import useSelfDMReport from '@hooks/useSelfDMReport';
 import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep';
-import useThemeStyles from '@hooks/useThemeStyles';
 import useWaypointItems from '@hooks/useWaypointItems';
 import {setMoneyRequestAmount, setMoneyRequestDistance, updateMoneyRequestDistance} from '@libs/actions/IOU';
 import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyRequest';
@@ -53,7 +46,6 @@ import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Nav
 import {roundToTwoDecimalPlaces} from '@libs/NumberUtils';
 import {isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtil} from '@libs/ReportUtils';
 import {getDistanceInMeters, getRateID, getRequestType, hasRoute, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
-import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import type {IOUType} from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -67,6 +59,8 @@ import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
 import type Transaction from '@src/types/onyx/Transaction';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import type TransactionStateType from '@src/types/utils/TransactionStateType';
+import DistanceManualTabContent from './DistanceManualTabContent';
+import DistanceMapTabContent from './DistanceMapTabContent';
 import StepScreenWrapper from './StepScreenWrapper';
 import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
 import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
@@ -86,7 +80,6 @@ function IOURequestStepDistance({
     transaction,
     currentUserPersonalDetails,
 }: IOURequestStepDistanceProps) {
-    const styles = useThemeStyles();
     const {isOffline} = useNetwork();
     const {translate} = useLocalize();
     const {isBetaEnabled} = usePermissions();
@@ -207,11 +200,16 @@ function IOURequestStepDistance({
         [distanceInMeters, distanceUnit],
     );
 
-    // For the manual distance input, prefer quantity (the user's manually edited value) over
-    // routes.route0.distance (the route-calculated value). This ensures that after a manual edit,
-    // re-opening the distance editor shows the manually entered value, not the route distance.
-    const quantity = currentTransaction?.comment?.customUnit?.quantity;
-    const manualInputDistance = useMemo(() => (quantity ? roundToTwoDecimalPlaces(quantity) : currentDistance), [quantity, currentDistance]);
+    // Sync the manual tab input when the route recalculates (e.g. after waypoint edits on the map tab).
+    // We track routes.route0.distance so this only fires on route changes, not on manual distance edits.
+    const routeDistance = currentTransaction?.routes?.route0?.distance;
+    useEffect(() => {
+        if (routeDistance == null || !distanceUnit) {
+            return;
+        }
+        const routeDistanceInUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistance, distanceUnit));
+        manualNumberFormRef.current?.updateNumber(routeDistanceInUnit.toString());
+    }, [routeDistance, distanceUnit]);
 
     // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
     const setDistanceRequestData = useCallback(
@@ -322,7 +320,8 @@ function IOURequestStepDistance({
         // goBack() without a route to first navigate within the tab navigator
         // (e.g. manual tab → map tab) instead of exiting the screen.
         // Provide an explicit fallback route to bypass the tab navigator.
-        const fallbackRoute = backTo ?? (isEditing ? ROUTES.REPORT_WITH_ID.getRoute(report?.reportID ?? reportID) : undefined);
+        const resolvedReportID = report?.reportID ?? reportID;
+        const fallbackRoute = backTo ?? (isEditing && resolvedReportID ? ROUTES.REPORT_WITH_ID.getRoute(resolvedReportID) : undefined);
         Navigation.goBack(fallbackRoute);
     }, [backTo, isEditing, report?.reportID, reportID]);
 
@@ -421,7 +420,7 @@ function IOURequestStepDistance({
         ownerBillingGraceEndPeriod,
     ]);
 
-    const getError = () => {
+    const getError = useCallback(() => {
         // Get route error if available else show the invalid number of waypoints error.
         if (hasRouteError) {
             return getLatestErrorField(currentTransaction, 'route');
@@ -436,7 +435,7 @@ function IOURequestStepDistance({
             return {atLeastTwoDifferentWaypointsError: translate('iou.error.atLeastTwoDifferentWaypoints')} as Errors;
         }
         return {};
-    };
+    }, [hasRouteError, currentTransaction, isWaypointsNullIslandError, translate, duplicateWaypointsError, atLeastTwoDifferentWaypointsError]);
 
     type DataParams = {
         data: string[];
@@ -510,7 +509,8 @@ function IOURequestStepDistance({
                 // When waypoints change, include the route-calculated distance so the server
                 // updates quantity to match the new route instead of keeping the old value.
                 const routeDistanceInMeters = transaction?.routes?.route0?.distance;
-                const routeDistanceInUnit = routeDistanceInMeters ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit)) : undefined;
+                const routeDistanceInUnit =
+                    routeDistanceInMeters != null ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit)) : undefined;
 
                 updateMoneyRequestDistance({
                     transactionID: transaction?.transactionID,
@@ -581,14 +581,14 @@ function IOURequestStepDistance({
         const distanceAsFloat = roundToTwoDecimalPlaces(parseFloat(value));
 
         if (isEditingSplit && transaction) {
-            setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
+            setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action, iouType), distanceUnit);
             setDraftSplitTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
             navigateBack();
             return;
         }
 
         const transactionDistanceUnit = currentTransaction?.comment?.customUnit?.distanceUnit;
-        const isDistanceChanged = manualInputDistance !== distanceAsFloat;
+        const isDistanceChanged = currentDistance !== distanceAsFloat;
         const isDistanceUnitChanged = transactionDistanceUnit && transactionDistanceUnit !== distanceUnit;
 
         // Check if waypoints were edited on the map tab before the user switched to manual.
@@ -628,6 +628,7 @@ function IOURequestStepDistance({
         distanceRate,
         transactionID,
         action,
+        iouType,
         distanceUnit,
         isEditingSplit,
         transaction,
@@ -635,7 +636,7 @@ function IOURequestStepDistance({
         splitDraftTransaction,
         policy,
         navigateBack,
-        manualInputDistance,
+        currentDistance,
         waypoints,
         transactionBackup,
         report,
@@ -664,84 +665,78 @@ function IOURequestStepDistance({
         [isLoadingRoute, navigateToWaypointEditPage, waypoints, getWaypointKey],
     );
 
-    const mapTabContent = (
-        <>
-            <View style={styles.flex1}>
-                <DraggableList
-                    data={waypointItems}
-                    keyExtractor={extractKey}
-                    onDragEnd={updateWaypoints}
-                    ref={scrollViewRef}
+    const handleManualInputChange = useCallback(() => {
+        if (!manualFormError) {
+            return;
+        }
+        setManualFormError('');
+    }, [manualFormError]);
+
+    const renderMapTab = useCallback(
+        () => (
+            <TabScreenWithFocusTrapWrapper>
+                <DistanceMapTabContent
+                    waypointItems={waypointItems}
+                    waypoints={waypoints}
+                    extractKey={extractKey}
+                    updateWaypoints={updateWaypoints}
+                    scrollViewRef={scrollViewRef}
                     renderItem={renderItem}
-                    ListFooterComponent={
-                        <DistanceRequestFooter
-                            waypoints={waypoints}
-                            navigateToWaypointEditPage={navigateToWaypointEditPage}
-                            transaction={transaction}
-                            policy={policy}
-                        />
-                    }
-                />
-            </View>
-            <View style={[styles.w100, styles.pt2]}>
-                {((shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError) && (
-                    <DotIndicatorMessage
-                        style={[styles.mh4, styles.mv3]}
-                        messages={getError()}
-                        type="error"
-                    />
-                )}
-                <Button
-                    success
-                    allowBubble
-                    pressOnEnter
-                    large
-                    style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
-                    onPress={submitWaypoints}
-                    text={buttonText}
-                    isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
-                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+                    navigateToWaypointEditPage={navigateToWaypointEditPage}
+                    transaction={transaction}
+                    policy={policy}
+                    shouldShowAtLeastTwoDifferentWaypointsError={shouldShowAtLeastTwoDifferentWaypointsError}
+                    atLeastTwoDifferentWaypointsError={atLeastTwoDifferentWaypointsError}
+                    duplicateWaypointsError={duplicateWaypointsError}
+                    hasRouteError={hasRouteError}
+                    getError={getError}
+                    submitWaypoints={submitWaypoints}
+                    buttonText={buttonText}
+                    isOffline={isOffline}
+                    isLoadingRoute={isLoadingRoute}
+                    shouldFetchRoute={shouldFetchRoute}
+                    isLoading={isLoading}
                 />
-            </View>
-        </>
+            </TabScreenWithFocusTrapWrapper>
+        ),
+        [
+            waypointItems,
+            waypoints,
+            extractKey,
+            updateWaypoints,
+            renderItem,
+            navigateToWaypointEditPage,
+            transaction,
+            policy,
+            shouldShowAtLeastTwoDifferentWaypointsError,
+            atLeastTwoDifferentWaypointsError,
+            duplicateWaypointsError,
+            hasRouteError,
+            getError,
+            submitWaypoints,
+            buttonText,
+            isOffline,
+            isLoadingRoute,
+            shouldFetchRoute,
+            isLoading,
+        ],
     );
 
-    const manualTabContent = (
-        <NumberWithSymbolForm
-            key={manualInputDistance}
-            ref={manualTextInputRef}
-            numberFormRef={manualNumberFormRef}
-            value={manualInputDistance?.toString()}
-            onInputChange={() => {
-                if (!manualFormError) {
-                    return;
-                }
-                setManualFormError('');
-            }}
-            decimals={CONST.DISTANCE_DECIMAL_PLACES}
-            symbol={distanceUnit}
-            symbolPosition={CONST.TEXT_INPUT_SYMBOL_POSITION.SUFFIX}
-            isSymbolPressable={false}
-            symbolTextStyle={styles.textSupporting}
-            style={styles.iouAmountTextInput}
-            containerStyle={styles.iouAmountTextInputContainer}
-            autoGrowExtraSpace={variables.w80}
-            touchableInputWrapperStyle={styles.heightUndefined}
-            errorText={manualFormError}
-            accessibilityLabel={`${translate('common.distance')} (${translate(`common.${distanceUnit}`)})`}
-            footer={
-                <Button
-                    success
-                    allowBubble={false}
-                    pressOnEnter
-                    large
-                    style={[styles.w100, styles.mt5]}
-                    onPress={submitManualDistance}
-                    text={translate('common.save')}
-                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+    const renderManualTab = useCallback(
+        () => (
+            <TabScreenWithFocusTrapWrapper>
+                <DistanceManualTabContent
+                    currentDistance={currentDistance}
+                    distanceUnit={distanceUnit}
+                    onSubmit={submitManualDistance}
+                    manualFormError={manualFormError}
+                    onInputChange={handleManualInputChange}
+                    manualTextInputRef={manualTextInputRef}
+                    manualNumberFormRef={manualNumberFormRef}
                 />
-            }
-        />
+            </TabScreenWithFocusTrapWrapper>
+        ),
+        [currentDistance, distanceUnit, submitManualDistance, manualFormError, handleManualInputChange],
     );
 
     if (isEditing) {
@@ -761,8 +756,8 @@ function IOURequestStepDistance({
                         defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
                         tabBar={TabSelector}
                     >
-                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{() => <TabScreenWithFocusTrapWrapper>{mapTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
-                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{() => <TabScreenWithFocusTrapWrapper>{manualTabContent}</TabScreenWithFocusTrapWrapper>}</TopTab.Screen>
+                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{renderMapTab}</TopTab.Screen>
+                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{renderManualTab}</TopTab.Screen>
                     </OnyxTabNavigator>
                 </FullPageNotFoundView>
             </ScreenWrapper>
@@ -777,7 +772,28 @@ function IOURequestStepDistance({
             shouldShowNotFoundPage={shouldShowNotFoundPage}
             shouldShowWrapper={!isCreatingNewRequest}
         >
-            {mapTabContent}
+            <DistanceMapTabContent
+                waypointItems={waypointItems}
+                waypoints={waypoints}
+                extractKey={extractKey}
+                updateWaypoints={updateWaypoints}
+                scrollViewRef={scrollViewRef}
+                renderItem={renderItem}
+                navigateToWaypointEditPage={navigateToWaypointEditPage}
+                transaction={transaction}
+                policy={policy}
+                shouldShowAtLeastTwoDifferentWaypointsError={shouldShowAtLeastTwoDifferentWaypointsError}
+                atLeastTwoDifferentWaypointsError={atLeastTwoDifferentWaypointsError}
+                duplicateWaypointsError={duplicateWaypointsError}
+                hasRouteError={hasRouteError}
+                getError={getError}
+                submitWaypoints={submitWaypoints}
+                buttonText={buttonText}
+                isOffline={isOffline}
+                isLoadingRoute={isLoadingRoute}
+                shouldFetchRoute={shouldFetchRoute}
+                isLoading={isLoading}
+            />
         </StepScreenWrapper>
     );
 }

From 46094189a919525bf2a4fb8bd89c435fa9486d06 Mon Sep 17 00:00:00 2001
From: Mukhriddin Shakhriyorov <mshakhriyorov8@gmail.com>
Date: Tue, 31 Mar 2026 08:23:37 +0500
Subject: [PATCH 13/31] Add tests for submitManualDistance validation and
 distance update behavior

---
 tests/ui/IOURequestStepDistanceTest.tsx | 137 +++++++++++++++++++++++-
 1 file changed, 135 insertions(+), 2 deletions(-)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index e2e5dd18fd16..99e4e856e7e7 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -2,7 +2,9 @@
 /* eslint-disable @typescript-eslint/no-unsafe-member-access */
 /* eslint-disable @typescript-eslint/no-unsafe-call */
 /* eslint-disable @typescript-eslint/no-unsafe-return */
-import {act, render, screen} from '@testing-library/react-native';
+/* eslint-disable @typescript-eslint/no-unsafe-argument */
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import {act, fireEvent, render, screen} from '@testing-library/react-native';
 import React from 'react';
 import Onyx from 'react-native-onyx';
 import {CurrentUserPersonalDetailsProvider} from '@components/CurrentUserPersonalDetailsProvider';
@@ -57,6 +59,8 @@ jest.mock('@libs/actions/IOU', () => {
         requestMoney: jest.fn(() => ({iouReport: undefined})),
         trackExpense: jest.fn(),
         createDistanceRequest: jest.fn(),
+        updateMoneyRequestDistance: jest.fn(),
+        setMoneyRequestDistance: jest.fn(),
     };
 });
 
@@ -101,6 +105,7 @@ jest.mock('@libs/Navigation/OnyxTabNavigator', () => {
         TabScreenWithFocusTrapWrapper,
     };
 });
+jest.mock('@hooks/useShowNotFoundPageInIOUStep', () => () => false);
 jest.mock('@src/hooks/useResponsiveLayout');
 
 jest.mock('@libs/Navigation/navigationRef', () => ({
@@ -184,7 +189,7 @@ function createTestReport(): Report {
     };
 }
 
-function createDistanceTransaction(): Transaction {
+function createDistanceTransaction(overrides?: Partial<Transaction>): Transaction {
     const transaction = createRandomTransaction(1);
     return {
         ...transaction,
@@ -197,10 +202,44 @@ function createDistanceTransaction(): Transaction {
                 waypoint0: {address: '123 Main St', lat: 40.7128, lng: -74.006, keyForList: 'start_waypoint'},
                 waypoint1: {address: '456 Oak Ave', lat: 40.7589, lng: -73.9851, keyForList: 'stop_waypoint'},
             },
+            customUnit: {
+                customUnitID: 'test-unit-id',
+                customUnitRateID: 'test-rate-id',
+                name: 'Distance',
+                distanceUnit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES,
+                quantity: 100,
+            },
+            ...overrides?.comment,
         },
+        ...overrides,
     };
 }
 
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function renderEditMode(_transaction: Transaction, _report: Report) {
+    return render(
+        <OnyxListItemProvider>
+            <CurrentUserPersonalDetailsProvider>
+                <IOURequestStepDistance
+                    route={{
+                        key: 'Money_Request_Step_Distance-test',
+                        name: SCREENS.MONEY_REQUEST.STEP_DISTANCE,
+                        params: {
+                            action: CONST.IOU.ACTION.EDIT as never,
+                            iouType: CONST.IOU.TYPE.SUBMIT,
+                            reportID: REPORT_ID,
+                            transactionID: TRANSACTION_ID,
+                            backTo: undefined as never,
+                        },
+                    }}
+                    // @ts-expect-error minimal navigation for test
+                    navigation={undefined}
+                />
+            </CurrentUserPersonalDetailsProvider>
+        </OnyxListItemProvider>,
+    );
+}
+
 describe('IOURequestStepDistance - draft transactions coverage', () => {
     beforeAll(() => {
         Onyx.init({
@@ -297,3 +336,97 @@ describe('IOURequestStepDistance - draft transactions coverage', () => {
         expect(screen.getByAccessibilityHint(/123 Main St/)).toBeTruthy();
     });
 });
+
+describe('IOURequestStepDistance - submitManualDistance', () => {
+    const {updateMoneyRequestDistance} = jest.requireMock<typeof IOU>('@libs/actions/IOU');
+
+    beforeAll(() => {
+        Onyx.init({
+            keys: ONYXKEYS,
+            evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
+        });
+    });
+
+    beforeEach(async () => {
+        jest.clearAllMocks();
+        await Onyx.clear();
+        await waitForBatchedUpdates();
+    });
+
+    it('should show validation error for empty distance input', async () => {
+        await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN);
+        const transaction = createDistanceTransaction();
+        const report = createTestReport();
+
+        await act(async () => {
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
+        });
+
+        renderEditMode(transaction, report);
+        await waitForBatchedUpdatesWithAct();
+
+        // The Save button from the manual tab should be visible (mock renders both tabs)
+        const saveButtons = screen.getAllByText('common.save');
+        expect(saveButtons.length).toBeGreaterThan(0);
+
+        // Press Save without changing the value — the form starts with the current distance
+        // so we need to clear it first to test empty validation
+        const distanceInput = screen.getAllByLabelText(/common\.distance/).at(0)!;
+        fireEvent.changeText(distanceInput, '');
+
+        fireEvent.press(saveButtons.at(0)!);
+
+        // updateMoneyRequestDistance should NOT have been called
+        expect(updateMoneyRequestDistance).not.toHaveBeenCalled();
+    });
+
+    it('should render manual distance input with current distance in edit mode', async () => {
+        await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN);
+        const transaction = createDistanceTransaction();
+        const report = createTestReport();
+
+        await act(async () => {
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
+        });
+
+        renderEditMode(transaction, report);
+        await waitForBatchedUpdatesWithAct();
+
+        // The manual distance input should display the current quantity value
+        const distanceInputs = screen.getAllByLabelText(/common\.distance/);
+        expect(distanceInputs.length).toBeGreaterThan(0);
+
+        // Save button should be visible
+        const saveButtons = screen.getAllByText('common.save');
+        expect(saveButtons.length).toBeGreaterThan(0);
+    });
+
+    it('should not call updateMoneyRequestDistance when Save is pressed without valid changes', async () => {
+        await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN);
+        const transaction = createDistanceTransaction();
+        const report = createTestReport();
+
+        await act(async () => {
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
+        });
+
+        renderEditMode(transaction, report);
+        await waitForBatchedUpdatesWithAct();
+
+        // Press Save — the distance value has not been changed via the number pad
+        const saveButtons = screen.getAllByText('common.save');
+        fireEvent.press(saveButtons.at(0)!);
+
+        // updateMoneyRequestDistance should not be called (either validation blocks or no-change early return)
+        expect(updateMoneyRequestDistance).not.toHaveBeenCalled();
+    });
+});

From 12117a5d79072cc0a2ffd582e9e5c40d19eb0adb Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Fri, 3 Apr 2026 17:57:25 +0500
Subject: [PATCH 14/31] temporary console log

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 3bb4c8b522b8..d3e86792f4c6 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -575,6 +575,7 @@ function IOURequestStepDistance({
             setManualFormError(translate('iou.error.invalidDistance'));
             return;
         }
+        console.log('value', value);
         if (!DistanceRequestUtils.isDistanceAmountWithinLimit(parseFloat(value), distanceRate)) {
             setManualFormError(translate('iou.error.distanceAmountTooLargeReduceDistance'));
             return;

From 6c31493750b8b0de592ac5a237f186914b017a7c Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Fri, 3 Apr 2026 18:01:18 +0500
Subject: [PATCH 15/31] removed console log

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index d3e86792f4c6..daed625087c2 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -575,7 +575,6 @@ function IOURequestStepDistance({
             setManualFormError(translate('iou.error.invalidDistance'));
             return;
         }
-        console.log('value', value);
         if (!DistanceRequestUtils.isDistanceAmountWithinLimit(parseFloat(value), distanceRate)) {
             setManualFormError(translate('iou.error.distanceAmountTooLargeReduceDistance'));
             return;
@@ -609,7 +608,7 @@ function IOURequestStepDistance({
 
         setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
         updateMoneyRequestDistance({
-            transactionID: transaction?.transactionID,
+            transaction,
             transactionThreadReport: report,
             parentReport,
             waypoints,

From 082c04e180f934dacd0d3573c1273cd76ccdef19 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sun, 5 Apr 2026 19:15:56 +0500
Subject: [PATCH 16/31] Update IS_LOADING_APP to RAM_ONLY_IS_LOADING_APP in
 IOURequestStepDistance tests

---
 tests/ui/IOURequestStepDistanceTest.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 99e4e856e7e7..4101a91e2d8c 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -362,7 +362,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
         });
 
         renderEditMode(transaction, report);
@@ -392,7 +392,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
         });
 
         renderEditMode(transaction, report);
@@ -416,7 +416,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
         });
 
         renderEditMode(transaction, report);

From a1973328b198e02a2b518e01cd2a2da7c77504eb Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Tue, 7 Apr 2026 07:21:16 +0500
Subject: [PATCH 17/31] Remove redundant setMoneyRequestDistance call and fix
 distance check condition

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 3 +--
 tests/ui/IOURequestStepDistanceTest.tsx               | 9 ++++-----
 2 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 1523ac240f57..a4db3af13e53 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -521,7 +521,7 @@ function IOURequestStepDistance({
                     waypoints,
                     recentWaypoints,
                     ...(hasRouteChanged ? {routes: transaction?.routes} : {}),
-                    ...(routeDistanceInUnit ? {distance: routeDistanceInUnit} : {}),
+                    ...(routeDistanceInUnit != null ? {distance: routeDistanceInUnit} : {}),
                     policy,
                     policyTagList: policyTags,
                     policyCategories,
@@ -605,7 +605,6 @@ function IOURequestStepDistance({
             return;
         }
 
-        setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action), distanceUnit);
         updateMoneyRequestDistance({
             transaction,
             transactionThreadReport: report,
diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 4101a91e2d8c..31e2acac906e 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -215,8 +215,7 @@ function createDistanceTransaction(overrides?: Partial<Transaction>): Transactio
     };
 }
 
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function renderEditMode(_transaction: Transaction, _report: Report) {
+function renderEditMode() {
     return render(
         <OnyxListItemProvider>
             <CurrentUserPersonalDetailsProvider>
@@ -365,7 +364,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
         });
 
-        renderEditMode(transaction, report);
+        renderEditMode();
         await waitForBatchedUpdatesWithAct();
 
         // The Save button from the manual tab should be visible (mock renders both tabs)
@@ -395,7 +394,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
         });
 
-        renderEditMode(transaction, report);
+        renderEditMode();
         await waitForBatchedUpdatesWithAct();
 
         // The manual distance input should display the current quantity value
@@ -419,7 +418,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
         });
 
-        renderEditMode(transaction, report);
+        renderEditMode();
         await waitForBatchedUpdatesWithAct();
 
         // Press Save — the distance value has not been changed via the number pad

From 986f1fd3cf33f7d3ee77972d8b2c9245cce77aa9 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Tue, 7 Apr 2026 07:26:54 +0500
Subject: [PATCH 18/31] Add test for updateMoneyRequestDistance when distance
 value differs from current transaction quantity

---
 tests/ui/IOURequestStepDistanceTest.tsx | 26 +++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 31e2acac906e..4a17ba15851b 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -428,4 +428,30 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
         // updateMoneyRequestDistance should not be called (either validation blocks or no-change early return)
         expect(updateMoneyRequestDistance).not.toHaveBeenCalled();
     });
+
+    it('should render both Map and Manual tabs with correct content in edit mode', async () => {
+        await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN);
+        const transaction = createDistanceTransaction();
+        const report = createTestReport();
+
+        await act(async () => {
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
+            await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
+            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
+        });
+
+        renderEditMode();
+        await waitForBatchedUpdatesWithAct();
+
+        // Map tab content: waypoint addresses should be visible
+        expect(screen.getByAccessibilityHint(/123 Main St/)).toBeTruthy();
+        expect(screen.getByAccessibilityHint(/456 Oak Ave/)).toBeTruthy();
+
+        // Manual tab content: distance input and Save button should be present
+        const distanceInputs = screen.getAllByLabelText(/common\.distance/);
+        expect(distanceInputs.length).toBeGreaterThan(0);
+        const saveButtons = screen.getAllByText('common.save');
+        expect(saveButtons.length).toBeGreaterThan(0);
+    });
 });

From 1d6cc8bd364fadb090a4d7a0a74f9eab131d2bc7 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sat, 11 Apr 2026 13:43:37 +0500
Subject: [PATCH 19/31] Update distance request UI with landscape support and
 improved manual editing\

---
 .../TransactionUtils/getDistanceInMeters.ts   |  10 +-
 .../request/step/DistanceManualTabContent.tsx |  28 ++++-
 .../request/step/DistanceMapTabContent.tsx    | 113 +++++++++++-------
 .../request/step/IOURequestStepDistance.tsx   |  75 +++++-------
 tests/ui/IOURequestStepDistanceTest.tsx       |   8 +-
 .../components/IOURequestStepDistanceTest.tsx |  20 ++--
 6 files changed, 136 insertions(+), 118 deletions(-)

diff --git a/src/libs/TransactionUtils/getDistanceInMeters.ts b/src/libs/TransactionUtils/getDistanceInMeters.ts
index 5afcdb5da79c..3f4a611abe0c 100644
--- a/src/libs/TransactionUtils/getDistanceInMeters.ts
+++ b/src/libs/TransactionUtils/getDistanceInMeters.ts
@@ -5,15 +5,15 @@ import type {Unit} from '@src/types/onyx/Policy';
 // Get the distance in meters from the transaction.
 // This function is placed in a separate file to avoid circular dependencies.
 function getDistanceInMeters(transaction: OnyxInputOrEntry<Transaction>, unit: Unit | undefined) {
-    // When quantity is set (manually edited or saved by the server), it is the authoritative distance.
-    // It takes priority over routes.route0.distance which may still hold a stale route-calculated
-    // value after a manual edit.
+    // If the request is completed, transaction.routes is cleared and comment.customUnit.quantity holds the new distance in the selected unit.
+    // We need to convert it from the selected distance unit to meters.
+    // This check takes priority because after a manual distance edit, routes.route0.distance may still
+    // hold a stale route-calculated value while quantity reflects the user's intended distance.
     if (transaction?.comment?.customUnit?.quantity && unit) {
         return DistanceRequestUtils.convertToDistanceInMeters(transaction.comment.customUnit.quantity, unit);
     }
 
-    // During the creation flow (before the transaction is saved), quantity is not yet set.
-    // The distance comes from the route calculation in routes.route0.distance (already in meters).
+    // If we are creating a new distance request, the distance is available in routes.route0.distance and it's already in meters.
     if (transaction?.routes?.route0?.distance) {
         return transaction.routes.route0.distance;
     }
diff --git a/src/pages/iou/request/step/DistanceManualTabContent.tsx b/src/pages/iou/request/step/DistanceManualTabContent.tsx
index 5014757be3e8..b32ce9b15f1a 100644
--- a/src/pages/iou/request/step/DistanceManualTabContent.tsx
+++ b/src/pages/iou/request/step/DistanceManualTabContent.tsx
@@ -1,10 +1,13 @@
-import React from 'react';
+import {useFocusEffect} from '@react-navigation/native';
+import React, {useCallback, useRef} from 'react';
 import Button from '@components/Button';
 import NumberWithSymbolForm from '@components/NumberWithSymbolForm';
 import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
 import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
 import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useThemeStyles from '@hooks/useThemeStyles';
+import {canUseTouchScreen} from '@libs/DeviceCapabilities';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import type {Unit} from '@src/types/onyx/Policy';
@@ -22,12 +25,27 @@ type DistanceManualTabContentProps = {
 function DistanceManualTabContent({currentDistance, distanceUnit, onSubmit, manualFormError, onInputChange, manualTextInputRef, manualNumberFormRef}: DistanceManualTabContentProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
+    const {isExtraSmallScreenHeight} = useResponsiveLayout();
+    const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+
+    useFocusEffect(
+        useCallback(() => {
+            focusTimeoutRef.current = setTimeout(() => manualTextInputRef.current?.focus(), CONST.ANIMATED_TRANSITION);
+            return () => {
+                if (!focusTimeoutRef.current) {
+                    return;
+                }
+                clearTimeout(focusTimeoutRef.current);
+            };
+        }, [manualTextInputRef]),
+    );
 
     return (
         <NumberWithSymbolForm
             ref={manualTextInputRef}
             numberFormRef={manualNumberFormRef}
             value={currentDistance?.toString()}
+            shouldUseDynamicFontSize
             onInputChange={onInputChange}
             decimals={CONST.DISTANCE_DECIMAL_PLACES}
             symbol={distanceUnit}
@@ -45,11 +63,13 @@ function DistanceManualTabContent({currentDistance, distanceUnit, onSubmit, manu
                     success
                     allowBubble={false}
                     pressOnEnter
-                    large
-                    style={[styles.w100, styles.mt5]}
+                    medium={isExtraSmallScreenHeight}
+                    large={!isExtraSmallScreenHeight}
+                    style={[styles.w100, canUseTouchScreen() ? styles.mt5 : styles.mt0]}
                     onPress={onSubmit}
                     text={translate('common.save')}
-                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
+                    testID="next-button"
+                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_MANUAL_NEXT_BUTTON}
                 />
             }
         />
diff --git a/src/pages/iou/request/step/DistanceMapTabContent.tsx b/src/pages/iou/request/step/DistanceMapTabContent.tsx
index 5294479fa4e9..3a32c5861dc4 100644
--- a/src/pages/iou/request/step/DistanceMapTabContent.tsx
+++ b/src/pages/iou/request/step/DistanceMapTabContent.tsx
@@ -8,6 +8,7 @@ import Button from '@components/Button';
 import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter';
 import DotIndicatorMessage from '@components/DotIndicatorMessage';
 import DraggableList from '@components/DraggableList';
+import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
 import useThemeStyles from '@hooks/useThemeStyles';
 import CONST from '@src/CONST';
 import type {Policy} from '@src/types/onyx';
@@ -15,6 +16,21 @@ import type {Errors} from '@src/types/onyx/OnyxCommon';
 import type Transaction from '@src/types/onyx/Transaction';
 import type {WaypointCollection} from '@src/types/onyx/Transaction';
 
+type ErrorState = {
+    shouldShowAtLeastTwoDifferentWaypointsError: boolean;
+    atLeastTwoDifferentWaypointsError: boolean;
+    duplicateWaypointsError: boolean;
+    hasRouteError: boolean;
+    getError: () => Errors;
+};
+
+type LoadingState = {
+    isOffline: boolean;
+    isLoadingRoute: boolean;
+    shouldFetchRoute: boolean;
+    isLoading: boolean;
+};
+
 type DistanceMapTabContentProps = {
     waypointItems: string[];
     waypoints: WaypointCollection;
@@ -25,17 +41,10 @@ type DistanceMapTabContentProps = {
     navigateToWaypointEditPage: (index: number) => void;
     transaction: OnyxEntry<Transaction>;
     policy: OnyxEntry<Policy>;
-    shouldShowAtLeastTwoDifferentWaypointsError: boolean;
-    atLeastTwoDifferentWaypointsError: boolean;
-    duplicateWaypointsError: boolean;
-    hasRouteError: boolean;
-    getError: () => Errors;
     submitWaypoints: () => void;
     buttonText: string;
-    isOffline: boolean;
-    isLoadingRoute: boolean;
-    shouldFetchRoute: boolean;
-    isLoading: boolean;
+    errorState: ErrorState;
+    loadingState: LoadingState;
 };
 
 function DistanceMapTabContent({
@@ -48,23 +57,32 @@ function DistanceMapTabContent({
     navigateToWaypointEditPage,
     transaction,
     policy,
-    shouldShowAtLeastTwoDifferentWaypointsError,
-    atLeastTwoDifferentWaypointsError,
-    duplicateWaypointsError,
-    hasRouteError,
-    getError,
     submitWaypoints,
     buttonText,
-    isOffline,
-    isLoadingRoute,
-    shouldFetchRoute,
-    isLoading,
+    errorState,
+    loadingState,
 }: DistanceMapTabContentProps) {
     const styles = useThemeStyles();
+    const isInLandscapeMode = useIsInLandscapeMode();
+
+    const {shouldShowAtLeastTwoDifferentWaypointsError, atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, getError} = errorState;
+    const {isOffline, isLoadingRoute, shouldFetchRoute, isLoading} = loadingState;
+    const hasError = (shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError;
 
     return (
-        <>
-            <View style={styles.flex1}>
+        <View style={[styles.flex1, isInLandscapeMode && styles.flexRow]}>
+            {isInLandscapeMode && (
+                <View style={styles.flex1}>
+                    <DistanceRequestFooter
+                        waypoints={waypoints}
+                        navigateToWaypointEditPage={navigateToWaypointEditPage}
+                        transaction={transaction}
+                        policy={policy}
+                        mapContainerStyle={{minHeight: undefined}}
+                    />
+                </View>
+            )}
+            <View style={[styles.flex1, isInLandscapeMode && styles.pl2]}>
                 <DraggableList
                     data={waypointItems}
                     keyExtractor={extractKey}
@@ -72,36 +90,39 @@ function DistanceMapTabContent({
                     ref={scrollViewRef}
                     renderItem={renderItem}
                     ListFooterComponent={
-                        <DistanceRequestFooter
-                            waypoints={waypoints}
-                            navigateToWaypointEditPage={navigateToWaypointEditPage}
-                            transaction={transaction}
-                            policy={policy}
-                        />
+                        !isInLandscapeMode ? (
+                            <DistanceRequestFooter
+                                waypoints={waypoints}
+                                navigateToWaypointEditPage={navigateToWaypointEditPage}
+                                transaction={transaction}
+                                policy={policy}
+                            />
+                        ) : undefined
                     }
                 />
-            </View>
-            <View style={[styles.w100, styles.pt2]}>
-                {((shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError) && (
-                    <DotIndicatorMessage
-                        style={[styles.mh4, styles.mv3]}
-                        messages={getError()}
-                        type="error"
+                <View style={[styles.w100, styles.pt2]}>
+                    {/* Show error message if there is route error or there are less than 2 routes and user has tried submitting */}
+                    {hasError && (
+                        <DotIndicatorMessage
+                            style={[styles.mh4, styles.mv3]}
+                            messages={getError()}
+                            type="error"
+                        />
+                    )}
+                    <Button
+                        success
+                        allowBubble
+                        pressOnEnter
+                        large
+                        style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
+                        onPress={submitWaypoints}
+                        text={buttonText}
+                        isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
+                        sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_MAP_NEXT_BUTTON}
                     />
-                )}
-                <Button
-                    success
-                    allowBubble
-                    pressOnEnter
-                    large
-                    style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
-                    onPress={submitWaypoints}
-                    text={buttonText}
-                    isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
-                    sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_NEXT_BUTTON}
-                />
+                </View>
             </View>
-        </>
+        </View>
     );
 }
 
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 018ba23ddf67..e2889167108f 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -203,11 +203,15 @@ function IOURequestStepDistance({
         [distanceInMeters, distanceUnit],
     );
 
+    // Track whether the user has started editing on the manual tab to prevent route updates
+    // from overwriting in-progress manual input.
+    const isManuallyEditing = useRef(false);
+
     // Sync the manual tab input when the route recalculates (e.g. after waypoint edits on the map tab).
-    // We track routes.route0.distance so this only fires on route changes, not on manual distance edits.
+    // Skip the sync if the user is actively editing on the manual tab to avoid overwriting their input.
     const routeDistance = currentTransaction?.routes?.route0?.distance;
     useEffect(() => {
-        if (routeDistance == null || !distanceUnit) {
+        if (routeDistance == null || !distanceUnit || isManuallyEditing.current) {
             return;
         }
         const routeDistanceInUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistance, distanceUnit));
@@ -338,11 +342,12 @@ function IOURequestStepDistance({
             if (isEditingSplit) {
                 iouWaypointType = CONST.IOU.TYPE.SPLIT_EXPENSE;
             }
-            Navigation.navigate(
-                ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, iouWaypointType, transactionID, report?.reportID ?? reportID, index.toString(), Navigation.getActiveRoute()),
-            );
+            const distanceBackTo = isEditing
+                ? ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouWaypointType, transactionID, report?.reportID ?? reportID)
+                : Navigation.getActiveRoute();
+            Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, iouWaypointType, transactionID, report?.reportID ?? reportID, index.toString(), distanceBackTo));
         },
-        [action, transactionID, report?.reportID, reportID, isEditingSplit],
+        [action, transactionID, report?.reportID, reportID, isEditingSplit, isEditing],
     );
 
     const navigateToNextStep = useCallback(() => {
@@ -571,6 +576,7 @@ function IOURequestStepDistance({
     ]);
 
     const submitManualDistance = useCallback(() => {
+        isManuallyEditing.current = false;
         const value = manualNumberFormRef.current?.getNumber() ?? '';
         if (!value.length || parseFloat(value) <= 0) {
             setManualFormError(translate('iou.error.invalidDistance'));
@@ -607,12 +613,14 @@ function IOURequestStepDistance({
             return;
         }
 
+        const hasRouteChanged = haveWaypointsChanged && !deepEqual(transactionBackup?.routes, transaction?.routes);
         updateMoneyRequestDistance({
             transaction,
             transactionThreadReport: report,
             parentReport,
             waypoints,
             distance: distanceAsFloat,
+            ...(hasRouteChanged ? {routes: transaction?.routes} : {}),
             transactionBackup,
             policy,
             policyTagList: policyTags,
@@ -668,12 +676,20 @@ function IOURequestStepDistance({
     );
 
     const handleManualInputChange = useCallback(() => {
+        isManuallyEditing.current = true;
         if (!manualFormError) {
             return;
         }
         setManualFormError('');
     }, [manualFormError]);
 
+    const errorState = useMemo(
+        () => ({shouldShowAtLeastTwoDifferentWaypointsError, atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, getError}),
+        [shouldShowAtLeastTwoDifferentWaypointsError, atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, getError],
+    );
+
+    const loadingState = useMemo(() => ({isOffline, isLoadingRoute, shouldFetchRoute, isLoading}), [isOffline, isLoadingRoute, shouldFetchRoute, isLoading]);
+
     const renderMapTab = useCallback(
         () => (
             <TabScreenWithFocusTrapWrapper>
@@ -687,41 +703,14 @@ function IOURequestStepDistance({
                     navigateToWaypointEditPage={navigateToWaypointEditPage}
                     transaction={transaction}
                     policy={policy}
-                    shouldShowAtLeastTwoDifferentWaypointsError={shouldShowAtLeastTwoDifferentWaypointsError}
-                    atLeastTwoDifferentWaypointsError={atLeastTwoDifferentWaypointsError}
-                    duplicateWaypointsError={duplicateWaypointsError}
-                    hasRouteError={hasRouteError}
-                    getError={getError}
                     submitWaypoints={submitWaypoints}
                     buttonText={buttonText}
-                    isOffline={isOffline}
-                    isLoadingRoute={isLoadingRoute}
-                    shouldFetchRoute={shouldFetchRoute}
-                    isLoading={isLoading}
+                    errorState={errorState}
+                    loadingState={loadingState}
                 />
             </TabScreenWithFocusTrapWrapper>
         ),
-        [
-            waypointItems,
-            waypoints,
-            extractKey,
-            updateWaypoints,
-            renderItem,
-            navigateToWaypointEditPage,
-            transaction,
-            policy,
-            shouldShowAtLeastTwoDifferentWaypointsError,
-            atLeastTwoDifferentWaypointsError,
-            duplicateWaypointsError,
-            hasRouteError,
-            getError,
-            submitWaypoints,
-            buttonText,
-            isOffline,
-            isLoadingRoute,
-            shouldFetchRoute,
-            isLoading,
-        ],
+        [waypointItems, waypoints, extractKey, updateWaypoints, renderItem, navigateToWaypointEditPage, transaction, policy, submitWaypoints, buttonText, errorState, loadingState],
     );
 
     const renderManualTab = useCallback(
@@ -742,7 +731,8 @@ function IOURequestStepDistance({
     );
 
     if (isEditing) {
-        const hasRequiredData = !!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage;
+        // Only show not-found when the transaction has loaded but waypoints are missing.
+        const hasRequiredData = !transaction || (!!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage);
         return (
             <ScreenWrapper
                 shouldEnableMaxHeight={canUseTouchScreen()}
@@ -784,17 +774,10 @@ function IOURequestStepDistance({
                 navigateToWaypointEditPage={navigateToWaypointEditPage}
                 transaction={transaction}
                 policy={policy}
-                shouldShowAtLeastTwoDifferentWaypointsError={shouldShowAtLeastTwoDifferentWaypointsError}
-                atLeastTwoDifferentWaypointsError={atLeastTwoDifferentWaypointsError}
-                duplicateWaypointsError={duplicateWaypointsError}
-                hasRouteError={hasRouteError}
-                getError={getError}
                 submitWaypoints={submitWaypoints}
                 buttonText={buttonText}
-                isOffline={isOffline}
-                isLoadingRoute={isLoadingRoute}
-                shouldFetchRoute={shouldFetchRoute}
-                isLoading={isLoading}
+                errorState={errorState}
+                loadingState={loadingState}
             />
         </StepScreenWrapper>
     );
diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 4a17ba15851b..08fa0b908874 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -361,7 +361,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
         });
 
         renderEditMode();
@@ -391,7 +391,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
         });
 
         renderEditMode();
@@ -415,7 +415,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
         });
 
         renderEditMode();
@@ -438,7 +438,7 @@ describe('IOURequestStepDistance - submitManualDistance', () => {
             await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction);
             await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, null);
-            await Onyx.merge(ONYXKEYS.RAM_ONLY_IS_LOADING_APP, false);
+            await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
         });
 
         renderEditMode();
diff --git a/tests/ui/components/IOURequestStepDistanceTest.tsx b/tests/ui/components/IOURequestStepDistanceTest.tsx
index 2586381a911f..19839181fd7b 100644
--- a/tests/ui/components/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/components/IOURequestStepDistanceTest.tsx
@@ -57,23 +57,17 @@ jest.mock('@components/ProductTrainingContext', () => ({
     useProductTrainingContext: () => [false],
 }));
 
+/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */
 jest.mock('@libs/Navigation/OnyxTabNavigator', () => {
-    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-    const React2 = require('react');
-    // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
-    const OnyxTabNavigator = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
-    const TopTab = {
-        // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
-        Screen: ({children}: {children: () => React.ReactNode}) => React2.createElement(React2.Fragment, null, typeof children === 'function' ? children() : children),
-    };
-    // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
-    const TabScreenWithFocusTrapWrapper = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
+    const ReactMock = require('react');
     return {
         // eslint-disable-next-line @typescript-eslint/naming-convention
         __esModule: true,
-        default: OnyxTabNavigator,
-        TopTab,
-        TabScreenWithFocusTrapWrapper,
+        default: ({children}: {children: React.ReactNode}) => ReactMock.createElement(ReactMock.Fragment, null, children),
+        TopTab: {
+            Screen: ({children}: {children: () => React.ReactNode}) => ReactMock.createElement(ReactMock.Fragment, null, typeof children === 'function' ? children() : children),
+        },
+        TabScreenWithFocusTrapWrapper: ({children}: {children: React.ReactNode}) => ReactMock.createElement(ReactMock.Fragment, null, children),
     };
 });
 

From eb72c5d20c7333b9eafc095853c68af49c2e816a Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sat, 11 Apr 2026 13:59:01 +0500
Subject: [PATCH 20/31] Update test mocks to use UpdateMoneyRequest module for
 updateMoneyRequestDistance

---
 tests/ui/IOURequestStepDistanceTest.tsx | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 08fa0b908874..94415396a08a 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -59,11 +59,13 @@ jest.mock('@libs/actions/IOU', () => {
         requestMoney: jest.fn(() => ({iouReport: undefined})),
         trackExpense: jest.fn(),
         createDistanceRequest: jest.fn(),
-        updateMoneyRequestDistance: jest.fn(),
-        setMoneyRequestDistance: jest.fn(),
     };
 });
 
+jest.mock('@libs/actions/IOU/UpdateMoneyRequest', () => ({
+    updateMoneyRequestDistance: jest.fn(),
+}));
+
 jest.mock('@libs/actions/IOU/MoneyRequest', () => ({
     handleMoneyRequestStepDistanceNavigation: jest.fn(),
 }));
@@ -337,7 +339,7 @@ describe('IOURequestStepDistance - draft transactions coverage', () => {
 });
 
 describe('IOURequestStepDistance - submitManualDistance', () => {
-    const {updateMoneyRequestDistance} = jest.requireMock<typeof IOU>('@libs/actions/IOU');
+    const {updateMoneyRequestDistance} = jest.requireMock<{updateMoneyRequestDistance: jest.Mock}>('@libs/actions/IOU/UpdateMoneyRequest');
 
     beforeAll(() => {
         Onyx.init({

From 40dc1be13d9f6136db5ada3589b806420027f82a Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sun, 19 Apr 2026 06:35:06 +0500
Subject: [PATCH 21/31] fixed distance RHP navigation bug

---
 .../request/step/DistanceManualTabContent.tsx | 24 +++++++------------
 .../request/step/IOURequestStepDistance.tsx   | 23 ++++++++----------
 2 files changed, 19 insertions(+), 28 deletions(-)

diff --git a/src/pages/iou/request/step/DistanceManualTabContent.tsx b/src/pages/iou/request/step/DistanceManualTabContent.tsx
index b32ce9b15f1a..0ac38cbad003 100644
--- a/src/pages/iou/request/step/DistanceManualTabContent.tsx
+++ b/src/pages/iou/request/step/DistanceManualTabContent.tsx
@@ -1,9 +1,9 @@
-import {useFocusEffect} from '@react-navigation/native';
-import React, {useCallback, useRef} from 'react';
+import React from 'react';
 import Button from '@components/Button';
 import NumberWithSymbolForm from '@components/NumberWithSymbolForm';
 import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
 import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
 import useLocalize from '@hooks/useLocalize';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useThemeStyles from '@hooks/useThemeStyles';
@@ -26,23 +26,17 @@ function DistanceManualTabContent({currentDistance, distanceUnit, onSubmit, manu
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const {isExtraSmallScreenHeight} = useResponsiveLayout();
-    const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+    const {inputCallbackRef} = useAutoFocusInput();
 
-    useFocusEffect(
-        useCallback(() => {
-            focusTimeoutRef.current = setTimeout(() => manualTextInputRef.current?.focus(), CONST.ANIMATED_TRANSITION);
-            return () => {
-                if (!focusTimeoutRef.current) {
-                    return;
-                }
-                clearTimeout(focusTimeoutRef.current);
-            };
-        }, [manualTextInputRef]),
-    );
+    const setInputRef = (ref: BaseTextInputRef | null) => {
+        // eslint-disable-next-line no-param-reassign
+        manualTextInputRef.current = ref;
+        inputCallbackRef(ref);
+    };
 
     return (
         <NumberWithSymbolForm
-            ref={manualTextInputRef}
+            ref={setInputRef}
             numberFormRef={manualNumberFormRef}
             value={currentDistance?.toString()}
             shouldUseDynamicFontSize
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index e2889167108f..73cc3e8cec02 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -323,14 +323,8 @@ function IOURequestStepDistance({
     }, []);
 
     const navigateBack = useCallback(() => {
-        // When editing, the OnyxTabNavigator's backBehavior="initialRoute" causes
-        // goBack() without a route to first navigate within the tab navigator
-        // (e.g. manual tab → map tab) instead of exiting the screen.
-        // Provide an explicit fallback route to bypass the tab navigator.
-        const resolvedReportID = report?.reportID ?? reportID;
-        const fallbackRoute = backTo ?? (isEditing && resolvedReportID ? ROUTES.REPORT_WITH_ID.getRoute(resolvedReportID) : undefined);
-        Navigation.goBack(fallbackRoute);
-    }, [backTo, isEditing, report?.reportID, reportID]);
+        Navigation.goBack(backTo);
+    }, [backTo]);
 
     /**
      * Takes the user to the page for editing a specific waypoint
@@ -342,12 +336,15 @@ function IOURequestStepDistance({
             if (isEditingSplit) {
                 iouWaypointType = CONST.IOU.TYPE.SPLIT_EXPENSE;
             }
-            const distanceBackTo = isEditing
-                ? ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouWaypointType, transactionID, report?.reportID ?? reportID)
-                : Navigation.getActiveRoute();
-            Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, iouWaypointType, transactionID, report?.reportID ?? reportID, index.toString(), distanceBackTo));
+            // Construct the backTo URL explicitly to match the stack entry created when we navigated
+            // to this distance edit page. Using Navigation.getActiveRoute() would return a URL with
+            // the OnyxTabNavigator's tab suffix (e.g. "/distance-map") which doesn't match the stack
+            // entry — causing Navigation.goBack() to REPLACE instead of POP and creating a duplicate
+            // distance page entry in the stack.
+            const waypointBackTo = ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, report?.reportID ?? reportID, backTo);
+            Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(action, iouWaypointType, transactionID, report?.reportID ?? reportID, index.toString(), waypointBackTo));
         },
-        [action, transactionID, report?.reportID, reportID, isEditingSplit, isEditing],
+        [action, iouType, transactionID, report?.reportID, reportID, backTo, isEditingSplit],
     );
 
     const navigateToNextStep = useCallback(() => {

From 77af6977b68c0ea678cc34f764e5cbc6d48ace46 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sun, 19 Apr 2026 06:42:07 +0500
Subject: [PATCH 22/31] fixed auto-focus type

---
 src/pages/iou/request/step/DistanceManualTabContent.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/pages/iou/request/step/DistanceManualTabContent.tsx b/src/pages/iou/request/step/DistanceManualTabContent.tsx
index 0ac38cbad003..64c95c67bd47 100644
--- a/src/pages/iou/request/step/DistanceManualTabContent.tsx
+++ b/src/pages/iou/request/step/DistanceManualTabContent.tsx
@@ -1,4 +1,5 @@
 import React from 'react';
+import type {TextInput} from 'react-native';
 import Button from '@components/Button';
 import NumberWithSymbolForm from '@components/NumberWithSymbolForm';
 import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
@@ -31,7 +32,7 @@ function DistanceManualTabContent({currentDistance, distanceUnit, onSubmit, manu
     const setInputRef = (ref: BaseTextInputRef | null) => {
         // eslint-disable-next-line no-param-reassign
         manualTextInputRef.current = ref;
-        inputCallbackRef(ref);
+        inputCallbackRef(ref as unknown as TextInput | null);
     };
 
     return (

From dbddf5a1b57a8e7726202586690b6bc70a89a623 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Mon, 27 Apr 2026 17:20:09 +0500
Subject: [PATCH 23/31] Removed the !transaction || short-circuit in
 hasRequiredData

---
 src/pages/iou/request/step/IOURequestStepDistance.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 73cc3e8cec02..89563bbe9db4 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -728,8 +728,7 @@ function IOURequestStepDistance({
     );
 
     if (isEditing) {
-        // Only show not-found when the transaction has loaded but waypoints are missing.
-        const hasRequiredData = !transaction || (!!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage);
+        const hasRequiredData = !!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage;
         return (
             <ScreenWrapper
                 shouldEnableMaxHeight={canUseTouchScreen()}

From b7e8613d3fcaafaa806e99692d80b6523861bf31 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Thu, 30 Apr 2026 11:31:32 +0500
Subject: [PATCH 24/31] Preserve full-precision distance to prevent false
 increasedDistance violations

---
 src/libs/actions/IOU/UpdateMoneyRequest.ts    | 34 ++++++++++++++++---
 .../request/step/IOURequestStepDistance.tsx   | 11 +++---
 2 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts
index df62faec5d24..9dad1322a49c 100644
--- a/src/libs/actions/IOU/UpdateMoneyRequest.ts
+++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts
@@ -1,3 +1,4 @@
+import {deepEqual} from 'fast-equals';
 // eslint-disable-next-line you-dont-need-lodash-underscore/union-by
 import lodashUnionBy from 'lodash/unionBy';
 import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
@@ -970,6 +971,12 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
 
     const dataToIncludeInParams: Partial<TransactionDetails> = Object.fromEntries(Object.entries(transactionDetails ?? {}).filter(([key]) => key in transactionChanges));
 
+    // Preserve the caller's full-precision distance so the server doesn't fire `increasedDistance`
+    // when the rounded display value drifts above the exact route distance.
+    if ('distance' in transactionChanges && typeof transactionChanges.distance === 'number') {
+        dataToIncludeInParams.distance = transactionChanges.distance;
+    }
+
     const apiParams: UpdateMoneyRequestParams = {
         ...dataToIncludeInParams,
         reportID: iouReport?.reportID,
@@ -977,6 +984,18 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     };
 
     const hasPendingWaypoints = 'waypoints' in transactionChanges;
+    // The manual-distance submit path always passes the current waypoints to keep the merge consistent,
+    // even when the user only edited the distance number. Compare against the existing transaction so we
+    // can tell apart a real waypoint edit (route invalidated) from a pure distance edit (route still valid).
+    const haveWaypointsActuallyChanged =
+        hasPendingWaypoints &&
+        (() => {
+            const oldWaypoints = transaction?.comment?.waypoints ?? {};
+            const newWaypoints = transactionChanges.waypoints ?? {};
+            const getAddresses = (collection: WaypointCollection) =>
+                Object.fromEntries(Object.entries(collection).map(([key, waypoint]) => [key, waypoint && 'address' in waypoint ? waypoint.address : undefined]));
+            return !deepEqual(getAddresses(oldWaypoints), getAddresses(newWaypoints));
+        })();
     const hasModifiedDistanceRate = 'customUnitRateID' in transactionChanges;
     const hasModifiedCreated = 'created' in transactionChanges;
     const hasModifiedAmount = 'amount' in transactionChanges;
@@ -1240,7 +1259,10 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
         apiParams.attendees = JSON.stringify(apiParams?.attendees);
     }
 
-    // Clear out the error fields and loading states on success
+    // Clear out the error fields and loading states on success.
+    // Only clear `routes` when waypoints/rate changed (the server will push a fresh route via Pusher).
+    // For pure distance edits the route is unchanged, and clearing it would make the map briefly disappear.
+    const shouldClearRoutes = haveWaypointsActuallyChanged || hasModifiedDistanceRate;
     successData.push({
         onyxMethod: Onyx.METHOD.MERGE,
         key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
@@ -1248,7 +1270,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
             pendingFields: clearedPendingFields,
             isLoading: false,
             errorFields: null,
-            routes: null,
+            ...(shouldClearRoutes && {routes: null}),
         },
     });
 
@@ -1317,9 +1339,13 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
         if (hasPendingWaypoints) {
             optimisticViolations = optimisticViolations.filter((violation) => violation.name !== CONST.VIOLATIONS.NO_ROUTE);
         }
-        if (hasModifiedDistanceRate || hasModifiedDistance) {
+        if (hasModifiedDistanceRate || hasModifiedDistance || hasPendingWaypoints) {
+            // Clear stale distance-related violations while the server reprocesses.
+            // The server will re-evaluate and re-add any that legitimately apply.
             optimisticViolations = optimisticViolations.filter(
-                (violation) => !(violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT && violation.data?.type === CONST.MODIFIED_AMOUNT_VIOLATION_DATA.DISTANCE),
+                (violation) =>
+                    !(violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT && violation.data?.type === CONST.MODIFIED_AMOUNT_VIOLATION_DATA.DISTANCE) &&
+                    violation.name !== CONST.VIOLATIONS.INCREASED_DISTANCE,
             );
         }
 
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 89563bbe9db4..b751a557f421 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -512,11 +512,11 @@ function IOURequestStepDistance({
                 return;
             }
             if (transaction?.transactionID && report?.reportID) {
-                // When waypoints change, include the route-calculated distance so the server
-                // updates quantity to match the new route instead of keeping the old value.
+                // Send the new route distance so the server replaces any stale manual quantity.
+                // Use full precision (no rounding) to avoid triggering the `increasedDistance`
+                // violation when the rounded display value drifts above the exact route distance.
                 const routeDistanceInMeters = transaction?.routes?.route0?.distance;
-                const routeDistanceInUnit =
-                    routeDistanceInMeters != null ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit)) : undefined;
+                const routeDistanceInUnit = routeDistanceInMeters != null ? DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit) : undefined;
 
                 updateMoneyRequestDistance({
                     transaction,
@@ -537,6 +537,9 @@ function IOURequestStepDistance({
                 });
             }
             transactionWasSaved.current = true;
+            // Remove the backup eagerly so the parent report view reads the optimistic transaction
+            // immediately, instead of the stale backup, while the API request is still in flight.
+            removeBackupTransaction(transaction?.transactionID);
             navigateBack();
             return;
         }

From c0e7fbc4c194935939d6e7f19b2661f3fbe8cadd Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Thu, 30 Apr 2026 12:51:04 +0500
Subject: [PATCH 25/31] fixed map vanishing bug

---
 src/libs/actions/IOU/UpdateMoneyRequest.ts    | 47 ++++++++++++-------
 .../request/step/IOURequestStepDistance.tsx   |  7 +++
 2 files changed, 37 insertions(+), 17 deletions(-)

diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts
index 9dad1322a49c..61057c143775 100644
--- a/src/libs/actions/IOU/UpdateMoneyRequest.ts
+++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts
@@ -943,20 +943,45 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
         >
     > = [];
 
+    // Step 2: Get all the collections being updated
+    const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
+
+    // The manual-distance submit path always sends waypoints to keep the BE in sync, even when the user
+    // only edited the distance number. Detect whether the addresses actually changed so we can skip the
+    // optimistic side effects (pending field, route clearing, render-path swap to interactive map) that
+    // would otherwise make the parent map briefly disappear on a pure distance edit.
+    const haveWaypointsActuallyChanged =
+        'waypoints' in transactionChanges &&
+        (() => {
+            const oldWaypoints = transaction?.comment?.waypoints ?? {};
+            const newWaypoints = transactionChanges.waypoints ?? {};
+            const getAddresses = (collection: WaypointCollection) =>
+                Object.fromEntries(Object.entries(collection).map(([key, waypoint]) => [key, waypoint && 'address' in waypoint ? waypoint.address : undefined]));
+            return !deepEqual(getAddresses(oldWaypoints), getAddresses(newWaypoints));
+        })();
+    const shouldSuppressWaypointsAsPending = 'waypoints' in transactionChanges && !haveWaypointsActuallyChanged;
+
     // Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData
-    const pendingFields: OnyxTypes.Transaction['pendingFields'] = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE]));
+    const pendingFields: OnyxTypes.Transaction['pendingFields'] = Object.fromEntries(
+        Object.keys(transactionChanges)
+            .filter((key) => !(shouldSuppressWaypointsAsPending && key === 'waypoints'))
+            .map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE]),
+    );
     const clearedPendingFields = getClearedPendingFields(transactionChanges);
     const errorFields = Object.fromEntries(Object.keys(pendingFields).map((key) => [key, getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage')]));
 
-    // Step 2: Get all the collections being updated
-    const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
-
     const isTransactionOnHold = isOnHold(transaction);
     const isFromExpenseReport = isExpenseReport(iouReport) || isInvoiceReportReportUtils(iouReport);
+    // Drop the waypoints from the changes fed to getUpdatedTransaction when they didn't actually change,
+    // so we skip the waypoints branch that flips isLoading and clobbers amount/merchant before being
+    // re-overridden by the distance branch.
+    const transactionChangesForOptimisticMerge: TransactionChanges = shouldSuppressWaypointsAsPending
+        ? Object.fromEntries(Object.entries(transactionChanges).filter(([key]) => key !== 'waypoints'))
+        : transactionChanges;
     const updatedTransaction: OnyxEntry<OnyxTypes.Transaction> = transaction
         ? getUpdatedTransaction({
               transaction,
-              transactionChanges,
+              transactionChanges: transactionChangesForOptimisticMerge,
               isFromExpenseReport,
               isSplitTransaction,
               policy,
@@ -984,18 +1009,6 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     };
 
     const hasPendingWaypoints = 'waypoints' in transactionChanges;
-    // The manual-distance submit path always passes the current waypoints to keep the merge consistent,
-    // even when the user only edited the distance number. Compare against the existing transaction so we
-    // can tell apart a real waypoint edit (route invalidated) from a pure distance edit (route still valid).
-    const haveWaypointsActuallyChanged =
-        hasPendingWaypoints &&
-        (() => {
-            const oldWaypoints = transaction?.comment?.waypoints ?? {};
-            const newWaypoints = transactionChanges.waypoints ?? {};
-            const getAddresses = (collection: WaypointCollection) =>
-                Object.fromEntries(Object.entries(collection).map(([key, waypoint]) => [key, waypoint && 'address' in waypoint ? waypoint.address : undefined]));
-            return !deepEqual(getAddresses(oldWaypoints), getAddresses(newWaypoints));
-        })();
     const hasModifiedDistanceRate = 'customUnitRateID' in transactionChanges;
     const hasModifiedCreated = 'created' in transactionChanges;
     const hasModifiedAmount = 'amount' in transactionChanges;
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index b751a557f421..d2a953af3b7a 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -209,8 +209,15 @@ function IOURequestStepDistance({
 
     // Sync the manual tab input when the route recalculates (e.g. after waypoint edits on the map tab).
     // Skip the sync if the user is actively editing on the manual tab to avoid overwriting their input.
+    // Skip the very first render so we don't clobber a previously-saved manual quantity with the route
+    // distance on edit re-open.
     const routeDistance = currentTransaction?.routes?.route0?.distance;
+    const isInitialRouteSync = useRef(true);
     useEffect(() => {
+        if (isInitialRouteSync.current) {
+            isInitialRouteSync.current = false;
+            return;
+        }
         if (routeDistance == null || !distanceUnit || isManuallyEditing.current) {
             return;
         }

From 4f14af9841a2b576c80296b1245d7c2705b77fc9 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Thu, 30 Apr 2026 13:30:08 +0500
Subject: [PATCH 26/31] fixed prettier

---
 tests/ui/IOURequestStepDistanceTest.tsx | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 94415396a08a..ad4031cd013e 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -2,7 +2,6 @@
 /* eslint-disable @typescript-eslint/no-unsafe-member-access */
 /* eslint-disable @typescript-eslint/no-unsafe-call */
 /* eslint-disable @typescript-eslint/no-unsafe-return */
-/* eslint-disable @typescript-eslint/no-unsafe-argument */
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
 import {act, fireEvent, render, screen} from '@testing-library/react-native';
 import React from 'react';

From 9ae271a04e112f4182a6dc76f805690daee5735d Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Fri, 1 May 2026 19:05:44 +0500
Subject: [PATCH 27/31] fixed navigating back afte save on mobile

---
 .../ReportActionItemImage.tsx                 |  8 +-
 src/libs/TransactionUtils/index.ts            |  7 ++
 src/libs/actions/IOU/UpdateMoneyRequest.ts    | 20 ++++-
 .../request/step/IOURequestStepDistance.tsx   | 64 ++++++-------
 .../step/IOURequestStepDistanceMap.tsx        | 89 ++++++-------------
 5 files changed, 83 insertions(+), 105 deletions(-)

diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx
index 400c0ab45e10..fd11424ac00a 100644
--- a/src/components/ReportActionItem/ReportActionItemImage.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImage.tsx
@@ -118,7 +118,13 @@ function ReportActionItemImage({
     const isMapDistanceRequest = !!transaction && isDistanceRequest(transaction) && !isManualDistanceRequest(transaction);
     const hasPendingWaypoints = transaction && isFetchingWaypointsFromServer(transaction);
     const hasErrors = !isEmptyObject(transaction?.errors) || !isEmptyObject(transaction?.errorFields?.route) || !isEmptyObject(transaction?.errorFields?.waypoints);
-    const showMapAsImage = isMapDistanceRequest && (hasErrors || hasPendingWaypoints);
+    // After a distance/rate edit the BE regenerates the receipt and invalidates the prior URL, but
+    // the local `receipt.source` only refreshes when the Pusher push arrives. Render `ConfirmedRoute`
+    // (which draws the map from `routes.coordinates`, independent of the URL) while any of these
+    // edits are pending so the thumbnail doesn't briefly try to load the now-404'd URL.
+    const pf = transaction?.pendingFields as Record<string, unknown> | undefined;
+    const hasPendingReceiptRegeneration = !!pf && (!!pf.distance || !!pf.merchant || !!pf.customUnitRateID);
+    const showMapAsImage = isMapDistanceRequest && (hasErrors || !!hasPendingWaypoints || hasPendingReceiptRegeneration);
 
     if (showMapAsImage) {
         return (
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index fd0d2a906b76..e60f08824c98 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -748,6 +748,13 @@ function getUpdatedTransaction({
             updatedTransaction.amount = updatedAmount;
             updatedTransaction.modifiedAmount = updatedAmount;
             updatedTransaction.modifiedMerchant = updatedMerchant;
+
+            // Sync `customUnit.quantity` to the new route distance. Without this the prior manual
+            // quantity (set when the user edited distance manually before changing waypoints) would
+            // linger and drive `getDistanceInMeters`, since that helper prefers quantity over routes.
+            if (unit) {
+                lodashSet(updatedTransaction, 'comment.customUnit.quantity', roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit)));
+            }
         }
     }
 
diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts
index c107f368c335..6e4c4edebbf4 100644
--- a/src/libs/actions/IOU/UpdateMoneyRequest.ts
+++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts
@@ -509,7 +509,10 @@ function updateMoneyRequestDistance({
         // Don't sanitize waypoints here - keep all fields for Onyx optimistic data (e.g., keyForList)
         // Sanitization happens when building API params
         ...(waypoints && {waypoints}),
-        routes,
+        // Only include routes when the caller explicitly provided them. Including `routes: undefined`
+        // would make the optimistic merge wipe the existing route, briefly blanking the map thumbnail
+        // and report preview before the server response restores it.
+        ...(routes !== undefined && {routes}),
         ...(distance && {distance}),
         ...(odometerStart !== undefined && {odometerStart}),
         ...(odometerEnd !== undefined && {odometerEnd}),
@@ -966,6 +969,15 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
             .filter((key) => !(shouldSuppressWaypointsAsPending && key === 'waypoints'))
             .map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE]),
     );
+    // Flag `merchant` as pending on any edit that causes the BE to regenerate the receipt
+    // (waypoints / distance / rate). `merchant` isn't in `transactionChanges`, so the success-data
+    // merge won't clear it via `clearedPendingFields` — it persists through the gap between API ack
+    // and the Pusher push that delivers the new `receipt.source`. The Pusher push then clears all
+    // pendingFields atomically together with the new URL, eliminating the broken-image flash.
+    // It also drives the Distance row's offline-feedback strikethrough for pure distance edits.
+    if ('waypoints' in transactionChanges || 'distance' in transactionChanges || 'customUnitRateID' in transactionChanges) {
+        pendingFields.merchant = CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;
+    }
     const clearedPendingFields = getClearedPendingFields(transactionChanges);
     const errorFields = Object.fromEntries(Object.keys(pendingFields).map((key) => [key, getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage')]));
 
@@ -1274,7 +1286,11 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     // Clear out the error fields and loading states on success.
     // Only clear `routes` when waypoints/rate changed (the server will push a fresh route via Pusher).
     // For pure distance edits the route is unchanged, and clearing it would make the map briefly disappear.
-    const shouldClearRoutes = haveWaypointsActuallyChanged || hasModifiedDistanceRate;
+    // When the caller already supplied a valid optimistic route (waypoint edit with route pre-fetched
+    // locally), keep it so the receipt thumbnail and ConfirmedRoute don't flicker between success and
+    // the Pusher route push.
+    const hasValidOptimisticRoute = !!transactionChanges.routes?.route0?.geometry?.coordinates?.length;
+    const shouldClearRoutes = (haveWaypointsActuallyChanged || hasModifiedDistanceRate) && !hasValidOptimisticRoute;
     successData.push({
         onyxMethod: Onyx.METHOD.MERGE,
         key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 408fd220b99b..05090ce30978 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -5,11 +5,8 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import type {ScrollView as RNScrollView} from 'react-native';
 import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types';
 import type {OnyxEntry} from 'react-native-onyx';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
 import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
 import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
-import ScreenWrapper from '@components/ScreenWrapper';
 import TabSelector from '@components/TabSelector/TabSelector';
 import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
 import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
@@ -37,7 +34,6 @@ import {init, stop} from '@libs/actions/MapboxToken';
 import {openReport} from '@libs/actions/Report';
 import {openDraftDistanceExpense, removeWaypoint, updateWaypoints as updateWaypointsUtil} from '@libs/actions/Transaction';
 import {createBackupTransaction, removeBackupTransaction, restoreOriginalTransactionFromBackup} from '@libs/actions/TransactionEdit';
-import {canUseTouchScreen} from '@libs/DeviceCapabilities';
 import DistanceRequestUtils from '@libs/DistanceRequestUtils';
 import type {MileageRate} from '@libs/DistanceRequestUtils';
 import {getLatestErrorField} from '@libs/ErrorUtils';
@@ -333,6 +329,10 @@ function IOURequestStepDistance({
         Navigation.goBack(backTo);
     }, [backTo]);
 
+    const navigateBackAfterSave = useCallback(() => {
+        Navigation.closeRHPFlow();
+    }, []);
+
     /**
      * Takes the user to the page for editing a specific waypoint
      * @param index of the waypoint to edit
@@ -503,7 +503,7 @@ function IOURequestStepDistance({
                     {waypoints: currentTransaction?.comment?.waypoints, routes: currentTransaction?.routes},
                     policy,
                 );
-                navigateBack();
+                navigateBackAfterSave();
                 return;
             }
 
@@ -515,16 +515,10 @@ function IOURequestStepDistance({
             const hasRouteChanged = !deepEqual(transactionBackup?.routes, transaction?.routes);
             if (deepEqual(oldAddresses, addresses)) {
                 transactionWasSaved.current = true;
-                navigateBack();
+                navigateBackAfterSave();
                 return;
             }
             if (transaction?.transactionID && report?.reportID) {
-                // Send the new route distance so the server replaces any stale manual quantity.
-                // Use full precision (no rounding) to avoid triggering the `increasedDistance`
-                // violation when the rounded display value drifts above the exact route distance.
-                const routeDistanceInMeters = transaction?.routes?.route0?.distance;
-                const routeDistanceInUnit = routeDistanceInMeters != null ? DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit) : undefined;
-
                 updateMoneyRequestDistance({
                     transaction,
                     transactionThreadReport: report,
@@ -532,7 +526,6 @@ function IOURequestStepDistance({
                     waypoints,
                     recentWaypoints,
                     ...(hasRouteChanged ? {routes: transaction?.routes} : {}),
-                    ...(routeDistanceInUnit != null ? {distance: routeDistanceInUnit} : {}),
                     policy,
                     policyTagList: policyTags,
                     policyCategories,
@@ -547,7 +540,7 @@ function IOURequestStepDistance({
             // Remove the backup eagerly so the parent report view reads the optimistic transaction
             // immediately, instead of the stale backup, while the API request is still in flight.
             removeBackupTransaction(transaction?.transactionID);
-            navigateBack();
+            navigateBackAfterSave();
             return;
         }
 
@@ -561,13 +554,13 @@ function IOURequestStepDistance({
         isLoading,
         isCreatingNewRequest,
         navigateToNextStep,
+        navigateBackAfterSave,
         isEditingSplit,
         originalSplitTransactionDraft,
         transactionBackup,
         waypoints,
         transaction,
         report,
-        navigateBack,
         currentTransaction?.comment?.waypoints,
         currentTransaction?.routes,
         policy,
@@ -579,7 +572,6 @@ function IOURequestStepDistance({
         currentUserEmailParam,
         isASAPSubmitBetaEnabled,
         parentReportNextStep,
-        distanceUnit,
     ]);
 
     const submitManualDistance = useCallback(() => {
@@ -599,7 +591,7 @@ function IOURequestStepDistance({
         if (isEditingSplit && transaction) {
             setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action, iouType), distanceUnit);
             setDraftSplitTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
-            navigateBack();
+            navigateBackAfterSave();
             return;
         }
 
@@ -616,7 +608,7 @@ function IOURequestStepDistance({
 
         if (!isDistanceChanged && !isDistanceUnitChanged && !haveWaypointsChanged) {
             transactionWasSaved.current = true;
-            navigateBack();
+            navigateBackAfterSave();
             return;
         }
 
@@ -639,7 +631,7 @@ function IOURequestStepDistance({
             recentWaypoints,
         });
         transactionWasSaved.current = true;
-        navigateBack();
+        navigateBackAfterSave();
     }, [
         translate,
         distanceRate,
@@ -652,7 +644,7 @@ function IOURequestStepDistance({
         currentTransaction?.comment?.customUnit?.distanceUnit,
         splitDraftTransaction,
         policy,
-        navigateBack,
+        navigateBackAfterSave,
         currentDistance,
         waypoints,
         transactionBackup,
@@ -738,27 +730,23 @@ function IOURequestStepDistance({
     );
 
     if (isEditing) {
-        const hasRequiredData = !!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage;
         return (
-            <ScreenWrapper
-                shouldEnableMaxHeight={canUseTouchScreen()}
+            <StepScreenWrapper
+                headerTitle={translate('common.distance')}
+                onBackButtonPress={navigateBack}
                 testID="IOURequestStepDistance"
+                shouldShowNotFoundPage={!currentTransaction?.comment?.waypoints || shouldShowNotFoundPage}
+                shouldShowWrapper
             >
-                <FullPageNotFoundView shouldShow={!hasRequiredData}>
-                    <HeaderWithBackButton
-                        title={translate('common.distance')}
-                        onBackButtonPress={navigateBack}
-                    />
-                    <OnyxTabNavigator
-                        id={CONST.TAB.DISTANCE_EDIT_TYPE}
-                        defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
-                        tabBar={TabSelector}
-                    >
-                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{renderMapTab}</TopTab.Screen>
-                        <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{renderManualTab}</TopTab.Screen>
-                    </OnyxTabNavigator>
-                </FullPageNotFoundView>
-            </ScreenWrapper>
+                <OnyxTabNavigator
+                    id={CONST.TAB.DISTANCE_EDIT_TYPE}
+                    defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
+                    tabBar={TabSelector}
+                >
+                    <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{renderMapTab}</TopTab.Screen>
+                    <TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{renderManualTab}</TopTab.Screen>
+                </OnyxTabNavigator>
+            </StepScreenWrapper>
         );
     }
 
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx
index 401385677370..23a971d78d07 100644
--- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx
@@ -1,21 +1,15 @@
 import {deepEqual} from 'fast-equals';
 import isEmpty from 'lodash/isEmpty';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
 // eslint-disable-next-line no-restricted-imports
 import type {ScrollView as RNScrollView} from 'react-native';
 import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types';
 import type {OnyxEntry} from 'react-native-onyx';
-import Button from '@components/Button';
-import DistanceRequestFooter from '@components/DistanceRequest/DistanceRequestFooter';
 import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem';
-import DotIndicatorMessage from '@components/DotIndicatorMessage';
-import DraggableList from '@components/DraggableList';
 import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
 import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
 import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy';
 import useFetchRoute from '@hooks/useFetchRoute';
-import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
 import useLocalize from '@hooks/useLocalize';
 import useNetwork from '@hooks/useNetwork';
 import useOnyx from '@hooks/useOnyx';
@@ -28,7 +22,6 @@ import useReportAttributes from '@hooks/useReportAttributes';
 import useReportIsArchived from '@hooks/useReportIsArchived';
 import useSelfDMReport from '@hooks/useSelfDMReport';
 import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep';
-import useThemeStyles from '@hooks/useThemeStyles';
 import useWaypointItems from '@hooks/useWaypointItems';
 import {setMoneyRequestAmount} from '@libs/actions/IOU';
 import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyRequest';
@@ -58,6 +51,7 @@ import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
 import type Transaction from '@src/types/onyx/Transaction';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import type TransactionStateType from '@src/types/utils/TransactionStateType';
+import DistanceMapTabContent from './DistanceMapTabContent';
 import StepScreenWrapper from './StepScreenWrapper';
 import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
 import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
@@ -77,8 +71,6 @@ function IOURequestStepDistanceMap({
     transaction,
     currentUserPersonalDetails,
 }: IOURequestStepDistanceMapProps) {
-    const styles = useThemeStyles();
-    const isInLandscapeMode = useIsInLandscapeMode();
     const {isOffline} = useNetwork();
     const {translate} = useLocalize();
     const {isBetaEnabled} = usePermissions();
@@ -378,7 +370,7 @@ function IOURequestStepDistanceMap({
         conciergeReportID,
     ]);
 
-    const getError = () => {
+    const getError = useCallback(() => {
         // Get route error if available else show the invalid number of waypoints error.
         if (hasRouteError) {
             return getLatestErrorField(currentTransaction, 'route');
@@ -393,7 +385,7 @@ function IOURequestStepDistanceMap({
             return {atLeastTwoDifferentWaypointsError: translate('iou.error.atLeastTwoDifferentWaypoints')} as Errors;
         }
         return {};
-    };
+    }, [hasRouteError, isWaypointsNullIslandError, duplicateWaypointsError, atLeastTwoDifferentWaypointsError, currentTransaction, translate]);
 
     type DataParams = {
         data: string[];
@@ -528,6 +520,13 @@ function IOURequestStepDistanceMap({
         [isLoadingRoute, navigateToWaypointEditPage, waypoints, getWaypointKey],
     );
 
+    const errorState = useMemo(
+        () => ({shouldShowAtLeastTwoDifferentWaypointsError, atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, getError}),
+        [shouldShowAtLeastTwoDifferentWaypointsError, atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, getError],
+    );
+
+    const loadingState = useMemo(() => ({isOffline, isLoadingRoute, shouldFetchRoute, isLoading}), [isOffline, isLoadingRoute, shouldFetchRoute, isLoading]);
+
     return (
         <StepScreenWrapper
             headerTitle={translate('common.distance')}
@@ -536,59 +535,21 @@ function IOURequestStepDistanceMap({
             shouldShowNotFoundPage={(isEditing && !currentTransaction?.comment?.waypoints) || shouldShowNotFoundPage}
             shouldShowWrapper={!isCreatingNewRequest}
         >
-            <View style={[styles.flex1, isInLandscapeMode && styles.flexRow]}>
-                {isInLandscapeMode && (
-                    <View style={styles.flex1}>
-                        <DistanceRequestFooter
-                            waypoints={waypoints}
-                            navigateToWaypointEditPage={navigateToWaypointEditPage}
-                            transaction={transaction}
-                            policy={policy}
-                            mapContainerStyle={{minHeight: undefined}}
-                        />
-                    </View>
-                )}
-                <View style={[styles.flex1, isInLandscapeMode && styles.pl2]}>
-                    <DraggableList
-                        data={waypointItems}
-                        keyExtractor={extractKey}
-                        onDragEnd={updateWaypoints}
-                        ref={scrollViewRef}
-                        renderItem={renderItem}
-                        ListFooterComponent={
-                            !isInLandscapeMode ? (
-                                <DistanceRequestFooter
-                                    waypoints={waypoints}
-                                    navigateToWaypointEditPage={navigateToWaypointEditPage}
-                                    transaction={transaction}
-                                    policy={policy}
-                                />
-                            ) : undefined
-                        }
-                    />
-                    <View style={[styles.w100, styles.pt2]}>
-                        {/* Show error message if there is route error or there are less than 2 routes and user has tried submitting, */}
-                        {((shouldShowAtLeastTwoDifferentWaypointsError && atLeastTwoDifferentWaypointsError) || duplicateWaypointsError || hasRouteError) && (
-                            <DotIndicatorMessage
-                                style={[styles.mh4, styles.mv3]}
-                                messages={getError()}
-                                type="error"
-                            />
-                        )}
-                        <Button
-                            success
-                            allowBubble
-                            pressOnEnter
-                            large
-                            style={[styles.w100, styles.mb5, styles.ph5, styles.flexShrink0]}
-                            onPress={submitWaypoints}
-                            text={buttonText}
-                            isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute || isLoading)}
-                            sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.DISTANCE_MAP_NEXT_BUTTON}
-                        />
-                    </View>
-                </View>
-            </View>
+            <DistanceMapTabContent
+                waypointItems={waypointItems}
+                waypoints={waypoints}
+                extractKey={extractKey}
+                updateWaypoints={updateWaypoints}
+                scrollViewRef={scrollViewRef}
+                renderItem={renderItem}
+                navigateToWaypointEditPage={navigateToWaypointEditPage}
+                transaction={transaction}
+                policy={policy}
+                submitWaypoints={submitWaypoints}
+                buttonText={buttonText}
+                errorState={errorState}
+                loadingState={loadingState}
+            />
         </StepScreenWrapper>
     );
 }

From ac9b9ed83e8a2335e2da4f8e3dd08a80a5593b14 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Fri, 1 May 2026 20:22:30 +0500
Subject: [PATCH 28/31] Extract waypoint address comparison into reusable
 utility function

---
 src/libs/TransactionUtils/index.ts                   | 12 ++++++++++++
 src/libs/actions/IOU/UpdateMoneyRequest.ts           | 12 ++----------
 .../iou/request/step/IOURequestStepDistance.tsx      | 12 +++---------
 .../iou/request/step/IOURequestStepDistanceMap.tsx   |  7 ++-----
 4 files changed, 19 insertions(+), 24 deletions(-)

diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 7bffb7ecc809..11c98d181f43 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -189,6 +189,17 @@ function hasGPSWaypoints(transaction: OnyxEntry<Transaction>) {
     return !!waypoint?.keyForList?.startsWith('gps');
 }
 
+/**
+ * Compare two waypoint collections by their addresses only (ignoring coordinates/names), which is
+ * the meaningful signal for "did the user change the route?". Numeric fields like lat/lng can drift
+ * due to rounding in transaction backups, so they're excluded.
+ */
+function haveWaypointAddressesChanged(oldWaypoints: WaypointCollection | undefined, newWaypoints: WaypointCollection | undefined): boolean {
+    const toAddresses = (collection: WaypointCollection | undefined) =>
+        Object.fromEntries(Object.entries(collection ?? {}).map(([key, waypoint]) => [key, waypoint && 'address' in waypoint ? waypoint.address : undefined]));
+    return !deepEqual(toAddresses(oldWaypoints), toAddresses(newWaypoints));
+}
+
 function isMapDistanceRequest(transaction: OnyxEntry<Transaction>): boolean {
     // This is used during the expense creation flow before the transaction has been saved to the server
     if (transaction && Object.hasOwn(transaction, 'iouRequestType')) {
@@ -2883,6 +2894,7 @@ export {
     isReceiptBeingScanned,
     didReceiptScanSucceed,
     getValidWaypoints,
+    haveWaypointAddressesChanged,
     isDistanceRequest,
     isMapDistanceRequest,
     isGPSDistanceRequest,
diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts
index 6e4c4edebbf4..2fcd8db5473f 100644
--- a/src/libs/actions/IOU/UpdateMoneyRequest.ts
+++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts
@@ -1,4 +1,3 @@
-import {deepEqual} from 'fast-equals';
 // eslint-disable-next-line you-dont-need-lodash-underscore/union-by
 import lodashUnionBy from 'lodash/unionBy';
 import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
@@ -29,6 +28,7 @@ import {
     getClearedPendingFields,
     getMerchant,
     getUpdatedTransaction,
+    haveWaypointAddressesChanged,
     isDistanceRequest as isDistanceRequestTransactionUtils,
     isFetchingWaypointsFromServer,
     isOnHold,
@@ -952,15 +952,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     // only edited the distance number. Detect whether the addresses actually changed so we can skip the
     // optimistic side effects (pending field, route clearing, render-path swap to interactive map) that
     // would otherwise make the parent map briefly disappear on a pure distance edit.
-    const haveWaypointsActuallyChanged =
-        'waypoints' in transactionChanges &&
-        (() => {
-            const oldWaypoints = transaction?.comment?.waypoints ?? {};
-            const newWaypoints = transactionChanges.waypoints ?? {};
-            const getAddresses = (collection: WaypointCollection) =>
-                Object.fromEntries(Object.entries(collection).map(([key, waypoint]) => [key, waypoint && 'address' in waypoint ? waypoint.address : undefined]));
-            return !deepEqual(getAddresses(oldWaypoints), getAddresses(newWaypoints));
-        })();
+    const haveWaypointsActuallyChanged = 'waypoints' in transactionChanges && haveWaypointAddressesChanged(transaction?.comment?.waypoints, transactionChanges.waypoints);
     const shouldSuppressWaypointsAsPending = 'waypoints' in transactionChanges && !haveWaypointsActuallyChanged;
 
     // Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 05090ce30978..0211eb24d04e 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -43,7 +43,7 @@ import Navigation from '@libs/Navigation/Navigation';
 import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator';
 import {roundToTwoDecimalPlaces} from '@libs/NumberUtils';
 import {isPolicyExpenseChat as isPolicyExpenseChatUtil} from '@libs/ReportUtils';
-import {getDistanceInMeters, getRateID, getRequestType, hasRoute, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
+import {getDistanceInMeters, getRateID, getRequestType, hasRoute, haveWaypointAddressesChanged, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import type {IOUType} from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -509,11 +509,8 @@ function IOURequestStepDistance({
 
             // If nothing was changed, simply go to transaction thread
             // We compare only addresses because numbers are rounded while backup
-            const oldWaypoints = transactionBackup?.comment?.waypoints ?? {};
-            const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
-            const addresses = Object.fromEntries(Object.entries(waypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
             const hasRouteChanged = !deepEqual(transactionBackup?.routes, transaction?.routes);
-            if (deepEqual(oldAddresses, addresses)) {
+            if (!haveWaypointAddressesChanged(transactionBackup?.comment?.waypoints, waypoints)) {
                 transactionWasSaved.current = true;
                 navigateBackAfterSave();
                 return;
@@ -601,10 +598,7 @@ function IOURequestStepDistance({
 
         // Check if waypoints were edited on the map tab before the user switched to manual.
         // If so, we must still send the update even if the distance value itself didn't change.
-        const oldWaypoints = transactionBackup?.comment?.waypoints ?? {};
-        const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
-        const currentAddresses = Object.fromEntries(Object.entries(waypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
-        const haveWaypointsChanged = !deepEqual(oldAddresses, currentAddresses);
+        const haveWaypointsChanged = haveWaypointAddressesChanged(transactionBackup?.comment?.waypoints, waypoints);
 
         if (!isDistanceChanged && !isDistanceUnitChanged && !haveWaypointsChanged) {
             transactionWasSaved.current = true;
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx
index 23a971d78d07..9033369a0298 100644
--- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx
@@ -38,7 +38,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import {shouldUseTransactionDraft} from '@libs/IOUUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import {isPolicyExpenseChat as isPolicyExpenseChatUtil} from '@libs/ReportUtils';
-import {getDistanceInMeters, getRateID, getRequestType, hasRoute, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
+import {getDistanceInMeters, getRateID, getRequestType, hasRoute, haveWaypointAddressesChanged, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -441,11 +441,8 @@ function IOURequestStepDistanceMap({
 
             // If nothing was changed, simply go to transaction thread
             // We compare only addresses because numbers are rounded while backup
-            const oldWaypoints = transactionBackup?.comment?.waypoints ?? {};
-            const oldAddresses = Object.fromEntries(Object.entries(oldWaypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
-            const addresses = Object.fromEntries(Object.entries(waypoints).map(([key, waypoint]) => [key, 'address' in waypoint ? waypoint.address : {}]));
             const hasRouteChanged = !deepEqual(transactionBackup?.routes, transaction?.routes);
-            if (deepEqual(oldAddresses, addresses)) {
+            if (!haveWaypointAddressesChanged(transactionBackup?.comment?.waypoints, waypoints)) {
                 navigateBack();
                 return;
             }

From 3eb6f4acbd31280eeb7894035b9c2eccc52d4f5f Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sat, 2 May 2026 09:58:11 +0500
Subject: [PATCH 29/31] fixed 404 not found and loading bug on IOU track
 distance fields

---
 src/libs/TransactionUtils/index.ts            | 27 +++++++++++++++----
 src/libs/actions/TransactionEdit.ts           |  4 ++-
 .../request/step/IOURequestStepDistance.tsx   | 20 ++++++++------
 3 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 11c98d181f43..14911cb48eb2 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -728,13 +728,17 @@ function getUpdatedTransaction({
         }
         shouldStopSmartscan = true;
 
-        if (!transactionChanges.routes?.route0?.geometry?.coordinates) {
+        // A manual-distance edit re-sends unchanged waypoints; when they truly didn't change, leave
+        // `amount`/`modifiedAmount` to the sibling `distance` branch instead of zeroing them here.
+        const waypointsActuallyChanged = !deepEqual(transactionChanges.waypoints, transaction?.comment?.waypoints);
+
+        if (waypointsActuallyChanged && !transactionChanges.routes?.route0?.geometry?.coordinates) {
             // The waypoints were changed, but there is no route – it is pending from the BE and we should mark the fields as pending
             updatedTransaction.amount = CONST.IOU.DEFAULT_AMOUNT;
             updatedTransaction.modifiedAmount = CONST.IOU.DEFAULT_AMOUNT;
             // eslint-disable-next-line @typescript-eslint/no-deprecated
             updatedTransaction.modifiedMerchant = translateLocal('iou.fieldPending');
-        } else {
+        } else if (transactionChanges.routes?.route0?.geometry?.coordinates) {
             const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy});
             const {unit, rate} = mileageRate;
 
@@ -881,6 +885,8 @@ function getUpdatedTransaction({
 
     if (Object.hasOwn(transactionChanges, 'distance') && typeof transactionChanges.distance === 'number') {
         const distance = roundToTwoDecimalPlaces(transactionChanges.distance ?? 0);
+        // Capture before mutating quantity below; needed by the fallback amount computation.
+        const previousDistanceInMeters = getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit);
 
         lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance);
         lodashSet(updatedTransaction, 'routes.route0.distance', null);
@@ -906,9 +912,20 @@ function getUpdatedTransaction({
             isManualDistanceRequest(transaction),
         );
 
-        updatedTransaction.modifiedAmount = amount;
-        updatedTransaction.modifiedMerchant = updatedMerchant;
-        updatedTransaction.modifiedCurrency = updatedCurrency;
+        // No locally resolvable rate (e.g. track expense without policy loaded) → scale the previous
+        // amount by the distance ratio so the optimistic value isn't 0. `modifiedAmount` is `""` for
+        // unedited transactions, so coerce via Number() and fall through to `amount`.
+        const previousAmount = Number(transaction?.modifiedAmount) || transaction?.amount || 0;
+        const useFallback = !rate && !!previousDistanceInMeters && !!previousAmount && !!distanceInMeters;
+        if (useFallback) {
+            updatedTransaction.modifiedAmount = Math.round(previousAmount * (distanceInMeters / previousDistanceInMeters));
+            updatedTransaction.modifiedMerchant = updatedMerchant;
+            // Leave currency alone — without a resolvable rate we don't know the target currency.
+        } else {
+            updatedTransaction.modifiedAmount = amount;
+            updatedTransaction.modifiedMerchant = updatedMerchant;
+            updatedTransaction.modifiedCurrency = updatedCurrency;
+        }
     }
 
     if (Object.hasOwn(transactionChanges, 'odometerStart') && typeof transactionChanges.odometerStart === 'number') {
diff --git a/src/libs/actions/TransactionEdit.ts b/src/libs/actions/TransactionEdit.ts
index 5bab719698c4..19dd5b73d737 100644
--- a/src/libs/actions/TransactionEdit.ts
+++ b/src/libs/actions/TransactionEdit.ts
@@ -39,7 +39,9 @@ function createBackupTransaction(transaction: OnyxEntry<Transaction>, isDraft: b
         key: `${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${transaction.transactionID}`,
         callback: (transactionBackup) => {
             Onyx.disconnect(conn);
-            if (transactionBackup) {
+            // Treat a backup missing `transactionID` as corrupted (a partial route-fetch shape can
+            // leak in via Pusher) and overwrite it instead of restoring from it.
+            if (transactionBackup?.transactionID) {
                 // If the transactionBackup exists it means we haven't properly restored original value on unmount
                 // such as on page refresh, so we will just restore the transaction from the transactionBackup here.
                 Onyx.set(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transactionBackup);
diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx
index 0211eb24d04e..8bdcc7a51373 100644
--- a/src/pages/iou/request/step/IOURequestStepDistance.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx
@@ -203,22 +203,23 @@ function IOURequestStepDistance({
     // from overwriting in-progress manual input.
     const isManuallyEditing = useRef(false);
 
-    // Sync the manual tab input when the route recalculates (e.g. after waypoint edits on the map tab).
-    // Skip the sync if the user is actively editing on the manual tab to avoid overwriting their input.
-    // Skip the very first render so we don't clobber a previously-saved manual quantity with the route
-    // distance on edit re-open.
+    // Push the route distance into the manual tab input only on a real waypoint-driven recalculation
+    // (a non-null → different non-null transition). Skip the initial null → value transition, which
+    // is the post-save re-fetch, so we don't overwrite a saved manual quantity with the route distance.
     const routeDistance = currentTransaction?.routes?.route0?.distance;
-    const isInitialRouteSync = useRef(true);
+    const lastSyncedRouteDistance = useRef<number | null | undefined>(routeDistance);
     useEffect(() => {
-        if (isInitialRouteSync.current) {
-            isInitialRouteSync.current = false;
+        if (routeDistance == null || !distanceUnit || isManuallyEditing.current) {
+            lastSyncedRouteDistance.current = routeDistance;
             return;
         }
-        if (routeDistance == null || !distanceUnit || isManuallyEditing.current) {
+        if (lastSyncedRouteDistance.current == null || lastSyncedRouteDistance.current === routeDistance) {
+            lastSyncedRouteDistance.current = routeDistance;
             return;
         }
         const routeDistanceInUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(routeDistance, distanceUnit));
         manualNumberFormRef.current?.updateNumber(routeDistanceInUnit.toString());
+        lastSyncedRouteDistance.current = routeDistance;
     }, [routeDistance, distanceUnit]);
 
     // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
@@ -625,6 +626,9 @@ function IOURequestStepDistance({
             recentWaypoints,
         });
         transactionWasSaved.current = true;
+        // Remove the backup eagerly so the parent report view reads the optimistic transaction
+        // immediately, instead of the stale backup, while the API request is still in flight.
+        removeBackupTransaction(transaction?.transactionID);
         navigateBackAfterSave();
     }, [
         translate,

From 3c18adb644dcf2c62c6b16bfc7e3fc00613bea17 Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Sat, 2 May 2026 15:00:05 +0500
Subject: [PATCH 30/31] changed hasWaypointAddressesChanged to
 haveWaypointsActuallyChanged

---
 src/libs/actions/IOU/UpdateMoneyRequest.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts
index 2fcd8db5473f..e843cd4e40bf 100644
--- a/src/libs/actions/IOU/UpdateMoneyRequest.ts
+++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts
@@ -952,8 +952,8 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     // only edited the distance number. Detect whether the addresses actually changed so we can skip the
     // optimistic side effects (pending field, route clearing, render-path swap to interactive map) that
     // would otherwise make the parent map briefly disappear on a pure distance edit.
-    const haveWaypointsActuallyChanged = 'waypoints' in transactionChanges && haveWaypointAddressesChanged(transaction?.comment?.waypoints, transactionChanges.waypoints);
-    const shouldSuppressWaypointsAsPending = 'waypoints' in transactionChanges && !haveWaypointsActuallyChanged;
+    const hasWaypointAddressesChanged = 'waypoints' in transactionChanges && haveWaypointAddressesChanged(transaction?.comment?.waypoints, transactionChanges.waypoints);
+    const shouldSuppressWaypointsAsPending = 'waypoints' in transactionChanges && !hasWaypointAddressesChanged;
 
     // Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData
     const pendingFields: OnyxTypes.Transaction['pendingFields'] = Object.fromEntries(
@@ -1282,7 +1282,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     // locally), keep it so the receipt thumbnail and ConfirmedRoute don't flicker between success and
     // the Pusher route push.
     const hasValidOptimisticRoute = !!transactionChanges.routes?.route0?.geometry?.coordinates?.length;
-    const shouldClearRoutes = (haveWaypointsActuallyChanged || hasModifiedDistanceRate) && !hasValidOptimisticRoute;
+    const shouldClearRoutes = (hasWaypointAddressesChanged || hasModifiedDistanceRate) && !hasValidOptimisticRoute;
     successData.push({
         onyxMethod: Onyx.METHOD.MERGE,
         key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,

From 222682ed591ae2c04c936210cdea3cbe5ef7d7fd Mon Sep 17 00:00:00 2001
From: Mukher <mshakhriyorov8@gmail.com>
Date: Wed, 6 May 2026 21:59:59 +0500
Subject: [PATCH 31/31] removed unused eslint comments and cleared wrong
 comment

---
 src/libs/actions/IOU/UpdateMoneyRequest.ts         | 4 ++--
 tests/ui/IOURequestStepDistanceTest.tsx            | 1 -
 tests/ui/components/IOURequestStepDistanceTest.tsx | 1 -
 3 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts
index 556e8eb0410b..4fd159668d87 100644
--- a/src/libs/actions/IOU/UpdateMoneyRequest.ts
+++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts
@@ -979,7 +979,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
         >
     > = [];
 
-    // Step 2: Get all the collections being updated
+    // Step 1: Get the transaction being updated
     const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
 
     // The manual-distance submit path always sends waypoints to keep the BE in sync, even when the user
@@ -989,7 +989,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
     const hasWaypointAddressesChanged = 'waypoints' in transactionChanges && haveWaypointAddressesChanged(transaction?.comment?.waypoints, transactionChanges.waypoints);
     const shouldSuppressWaypointsAsPending = 'waypoints' in transactionChanges && !hasWaypointAddressesChanged;
 
-    // Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData
+    // Step 2: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData
     const pendingFields: OnyxTypes.Transaction['pendingFields'] = Object.fromEntries(
         Object.keys(transactionChanges)
             .filter((key) => !(shouldSuppressWaypointsAsPending && key === 'waypoints'))
diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx
index 587b0aa3d1d3..1b0a985be138 100644
--- a/tests/ui/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/IOURequestStepDistanceTest.tsx
@@ -99,7 +99,6 @@ jest.mock('@libs/Navigation/OnyxTabNavigator', () => {
     };
     const TabScreenWithFocusTrapWrapper = ({children}: {children: React.ReactNode}) => React2.createElement(React2.Fragment, null, children);
     return {
-        // eslint-disable-next-line @typescript-eslint/naming-convention
         __esModule: true,
         default: OnyxTabNavigator,
         TopTab,
diff --git a/tests/ui/components/IOURequestStepDistanceTest.tsx b/tests/ui/components/IOURequestStepDistanceTest.tsx
index fece9e68f279..3b5716c15195 100644
--- a/tests/ui/components/IOURequestStepDistanceTest.tsx
+++ b/tests/ui/components/IOURequestStepDistanceTest.tsx
@@ -61,7 +61,6 @@ jest.mock('@components/ProductTrainingContext', () => ({
 jest.mock('@libs/Navigation/OnyxTabNavigator', () => {
     const ReactMock = require('react');
     return {
-        // eslint-disable-next-line @typescript-eslint/naming-convention
         __esModule: true,
         default: ({children}: {children: React.ReactNode}) => ReactMock.createElement(ReactMock.Fragment, null, children),
         TopTab: {
