From a769c887712bcfda36f9698acb0e2160df520f2c Mon Sep 17 00:00:00 2001
From: Ben Limpich <blimpich@expensify.com>
Date: Thu, 28 May 2026 10:19:51 -0700
Subject: [PATCH 1/4] Drop URN-prefixed station codes from rail trip display

---
 src/libs/TripReservationUtils.ts       |  7 ++--
 tests/unit/TripReservationUtilsTest.ts | 47 ++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index e4badc79e01e..df10064e0549 100644
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -336,6 +336,9 @@ function getCarReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem
     return reservationList;
 }
 
+// Trainline-sourced rail legs put a URN (e.g. "urn:trainline:public:nloc:at000408") in `code` instead of a short station identifier — strip those so the UI doesn't render the URN as a station label.
+const getRailStationShortName = (code: string | undefined) => (code && !code.startsWith('urn:') ? code : '');
+
 function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem[] {
     const reservationList: ReservationItem[] = [];
 
@@ -362,13 +365,13 @@ function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationIte
                     start: {
                         date: leg.departAt.iso8601,
                         longName: leg.originInfo.name,
-                        shortName: leg.originInfo.code,
+                        shortName: getRailStationShortName(leg.originInfo.code),
                         cityName: leg.originInfo.cityName,
                     },
                     end: {
                         date: leg.arriveAt.iso8601,
                         longName: leg.destinationInfo.name,
-                        shortName: leg.destinationInfo.code,
+                        shortName: getRailStationShortName(leg.destinationInfo.code),
                         cityName: leg.destinationInfo.cityName,
                     },
                     route: {
diff --git a/tests/unit/TripReservationUtilsTest.ts b/tests/unit/TripReservationUtilsTest.ts
index 4addabcc12a8..b138405209c1 100644
--- a/tests/unit/TripReservationUtilsTest.ts
+++ b/tests/unit/TripReservationUtilsTest.ts
@@ -2692,6 +2692,53 @@ describe('TripReservationUtils', () => {
         });
     });
 
+    describe('rail shortName sanitization', () => {
+        it('should drop a URN-formatted code from rail shortName', () => {
+            const railPnrWithUrnCodes = JSON.parse(JSON.stringify(railPnr)) as typeof railPnr;
+            const leg = railPnrWithUrnCodes.data.railPnr?.legInfos.at(0);
+            if (leg) {
+                leg.originInfo.code = 'urn:trainline:public:nloc:at000408';
+                leg.destinationInfo.code = 'urn:trainline:public:nloc:at001685';
+            }
+
+            const report = createRandomReport(1, undefined);
+            report.tripData = {
+                tripID: 'trip123',
+                payload: {
+                    ...basicTripData,
+                    pnrs: [railPnrWithUrnCodes],
+                },
+            };
+
+            const result = getReservationsFromTripReport(report, []);
+            expect(result).toHaveLength(1);
+
+            const trainReservation = result.at(0)?.reservation;
+            expect(trainReservation?.type).toEqual(CONST.RESERVATION_TYPE.TRAIN);
+            expect(trainReservation?.start?.shortName).toEqual('');
+            expect(trainReservation?.end?.shortName).toEqual('');
+        });
+
+        it('should preserve a clean station code in rail shortName', () => {
+            const report = createRandomReport(1, undefined);
+            report.tripData = {
+                tripID: 'trip123',
+                payload: {
+                    ...basicTripData,
+                    pnrs: [railPnr],
+                },
+            };
+
+            const result = getReservationsFromTripReport(report, []);
+            expect(result).toHaveLength(1);
+
+            const trainReservation = result.at(0)?.reservation;
+            expect(trainReservation?.type).toEqual(CONST.RESERVATION_TYPE.TRAIN);
+            expect(trainReservation?.start?.shortName).toEqual('STX');
+            expect(trainReservation?.end?.shortName).toEqual('STY');
+        });
+    });
+
     describe('getPNRReservationDataFromTripReport', () => {
         it('should return an empty array when there are no transactions and trip payload', () => {
             const report = createRandomReport(1, undefined);

From 477ca297bea52a8c58c64dde58129351491fcf69 Mon Sep 17 00:00:00 2001
From: Ben Limpich <blimpich@expensify.com>
Date: Thu, 28 May 2026 10:30:50 -0700
Subject: [PATCH 2/4] Route rail station labels through formatAirportInfo so
 empty short codes don't render as empty parens

---
 src/libs/TripReservationUtils.ts       | 12 ++++++------
 src/pages/Travel/TrainTripDetails.tsx  |  9 ++++-----
 tests/unit/TripReservationUtilsTest.ts | 10 +++++++++-
 3 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index df10064e0549..a3e0d7a69923 100644
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -475,12 +475,12 @@ function getReservationsFromTripReport(tripReport?: Report, transactions?: Trans
 }
 
 function formatAirportInfo(reservationTimeDetails: ReservationTimeDetails, hideAirportCode = false): string {
-    const longName = reservationTimeDetails?.longName ? `${reservationTimeDetails?.longName} ` : '';
-    let shortName = reservationTimeDetails?.shortName ? `${reservationTimeDetails?.shortName}` : '';
-
-    shortName = longName && shortName ? `(${shortName})` : shortName;
-
-    return !hideAirportCode ? `${longName}${shortName}` : longName;
+    const longName = reservationTimeDetails?.longName ?? '';
+    const shortName = reservationTimeDetails?.shortName ?? '';
+    if (hideAirportCode || !shortName) {
+        return longName;
+    }
+    return longName ? `${longName} (${shortName})` : `(${shortName})`;
 }
 
 function getPNRReservationDataFromTripReport(tripReport?: Report, transactions?: Transaction[]): ReservationPNRData[] {
diff --git a/src/pages/Travel/TrainTripDetails.tsx b/src/pages/Travel/TrainTripDetails.tsx
index aae615a0c757..3296de4ea6f0 100644
--- a/src/pages/Travel/TrainTripDetails.tsx
+++ b/src/pages/Travel/TrainTripDetails.tsx
@@ -7,6 +7,7 @@ import UserPills from '@components/UserPills';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import DateUtils from '@libs/DateUtils';
+import {formatAirportInfo} from '@libs/TripReservationUtils';
 import CONST from '@src/CONST';
 import type {PersonalDetails} from '@src/types/onyx';
 import type {Reservation} from '@src/types/onyx/Transaction';
@@ -22,9 +23,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
 
     const startDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.start.date));
     const endDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.end.date));
-    const trainRouteDescription = `${reservation.start.longName} (${reservation.start.shortName}) ${translate('common.conjunctionTo')} ${reservation.end.longName} (${
-        reservation.end.shortName
-    })`;
+    const trainRouteDescription = `${formatAirportInfo(reservation.start)} ${translate('common.conjunctionTo')} ${formatAirportInfo(reservation.end)}`;
     const trainDuration = DateUtils.getFormattedDurationBetweenDates(translate, new Date(reservation.start.date), new Date(reservation.end.date));
 
     const displayName = personalDetails?.displayName ?? reservation.travelerPersonalInfo?.name;
@@ -50,7 +49,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
                 description={translate('travel.trainDetails.departs')}
                 descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
                 titleComponent={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{startDate.hour}</Text>}
-                helperText={`${reservation.start.longName} (${reservation.start.shortName})`}
+                helperText={formatAirportInfo(reservation.start)}
                 helperTextStyle={[styles.pb3, styles.mtn2]}
                 interactive={false}
             />
@@ -58,7 +57,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
                 description={translate('travel.trainDetails.arrives')}
                 descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
                 titleComponent={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{endDate.hour}</Text>}
-                helperText={`${reservation.end.longName} (${reservation.end.shortName})`}
+                helperText={formatAirportInfo(reservation.end)}
                 helperTextStyle={[styles.pb3, styles.mtn2]}
                 interactive={false}
             />
diff --git a/tests/unit/TripReservationUtilsTest.ts b/tests/unit/TripReservationUtilsTest.ts
index b138405209c1..fa0867d44dc1 100644
--- a/tests/unit/TripReservationUtilsTest.ts
+++ b/tests/unit/TripReservationUtilsTest.ts
@@ -1,5 +1,5 @@
 /* cspell:disable */
-import {getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
+import {formatAirportInfo, getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
 import CONST from '@src/CONST';
 import type {Pnr, TripData} from '@src/types/onyx/TripData';
 import {airReservationPnrData, airReservationTravelers} from '../data/TripAirReservationData';
@@ -2719,6 +2719,14 @@ describe('TripReservationUtils', () => {
             expect(trainReservation?.end?.shortName).toEqual('');
         });
 
+        it('should render only longName when shortName is empty (no trailing space, no empty parens)', () => {
+            expect(formatAirportInfo({date: '', longName: 'Brockenhurst', shortName: '', cityName: ''})).toEqual('Brockenhurst');
+        });
+
+        it('should render longName with parenthesized shortName when both are present', () => {
+            expect(formatAirportInfo({date: '', longName: 'Solana Beach station', shortName: 'SOL', cityName: ''})).toEqual('Solana Beach station (SOL)');
+        });
+
         it('should preserve a clean station code in rail shortName', () => {
             const report = createRandomReport(1, undefined);
             report.tripData = {

From 57838543468c2e5aa7f3a6a588021f7c317c2146 Mon Sep 17 00:00:00 2001
From: Ben Limpich <blimpich@expensify.com>
Date: Thu, 28 May 2026 10:31:16 -0700
Subject: [PATCH 3/4] Reword rail URN comment to clear cspell

---
 src/libs/TripReservationUtils.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index a3e0d7a69923..dbf357aa262e 100644
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -336,7 +336,7 @@ function getCarReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem
     return reservationList;
 }
 
-// Trainline-sourced rail legs put a URN (e.g. "urn:trainline:public:nloc:at000408") in `code` instead of a short station identifier — strip those so the UI doesn't render the URN as a station label.
+// Drop Trainline URN-style codes so the trip UI doesn't render them as station labels.
 const getRailStationShortName = (code: string | undefined) => (code && !code.startsWith('urn:') ? code : '');
 
 function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem[] {

From ad3416a39a48eab5a35876ddb630d53d00018561 Mon Sep 17 00:00:00 2001
From: Ben Limpich <blimpich@expensify.com>
Date: Thu, 28 May 2026 12:30:13 -0700
Subject: [PATCH 4/4] Rename formatAirportInfo to formatTransitLocationLabel

---
 src/components/ReportActionItem/TripDetailsView.tsx | 10 +++++-----
 src/libs/TripReservationUtils.ts                    |  6 +++---
 src/pages/Travel/TrainTripDetails.tsx               |  8 ++++----
 tests/unit/TripReservationUtilsTest.ts              |  6 +++---
 4 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx
index 9216928033ba..235a37d7f52d 100644
--- a/src/components/ReportActionItem/TripDetailsView.tsx
+++ b/src/components/ReportActionItem/TripDetailsView.tsx
@@ -22,7 +22,7 @@ import StringUtils from '@libs/StringUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import type {ReservationData} from '@src/libs/TripReservationUtils';
-import {formatAirportInfo, formatCancelledDescription, getPNRReservationDataFromTripReport, getTripReservationCode, getTripReservationIcon} from '@src/libs/TripReservationUtils';
+import {formatCancelledDescription, formatTransitLocationLabel, getPNRReservationDataFromTripReport, getTripReservationCode, getTripReservationIcon} from '@src/libs/TripReservationUtils';
 import ROUTES from '@src/ROUTES';
 import type {Report} from '@src/types/onyx';
 import type {Reservation} from '@src/types/onyx/Transaction';
@@ -102,18 +102,18 @@ function ReservationView({reservation, transactionID, tripRoomReportID, sequence
                     <View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
                         {shouldShowArrowIcon ? (
                             <>
-                                <Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatAirportInfo(reservation.start)}</Text>
+                                <Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatTransitLocationLabel(reservation.start)}</Text>
                                 <Icon
                                     src={expensifyIcons.ArrowRightLong}
                                     width={variables.iconSizeSmall}
                                     height={variables.iconSizeSmall}
                                     fill={theme.icon}
                                 />
-                                <Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatAirportInfo(reservation.end)}</Text>
+                                <Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatTransitLocationLabel(reservation.end)}</Text>
                             </>
                         ) : (
                             <Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>
-                                {formatAirportInfo(reservation.start)} {translate('common.to').toLowerCase()} {formatAirportInfo(reservation.end)}
+                                {formatTransitLocationLabel(reservation.start)} {translate('common.to').toLowerCase()} {formatTransitLocationLabel(reservation.end)}
                             </Text>
                         )}
                     </View>
@@ -217,7 +217,7 @@ function TripDetailsView({tripRoomReport, shouldShowHorizontalRule, tripTransact
                     if (!destinationReservation) {
                         return '';
                     }
-                    return `${translate('travel.flightTo')} ${formatAirportInfo(destinationReservation.reservation.end, true)}`;
+                    return `${translate('travel.flightTo')} ${formatTransitLocationLabel(destinationReservation.reservation.end, true)}`;
                 }
                 case CONST.RESERVATION_TYPE.TRAIN:
                     if (reservations.length === 2 && firstReservation.start.shortName === lastReservation.end.shortName) {
diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index dbf357aa262e..5c18405ed428 100644
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -474,10 +474,10 @@ function getReservationsFromTripReport(tripReport?: Report, transactions?: Trans
     return [];
 }
 
-function formatAirportInfo(reservationTimeDetails: ReservationTimeDetails, hideAirportCode = false): string {
+function formatTransitLocationLabel(reservationTimeDetails: ReservationTimeDetails, hideShortCode = false): string {
     const longName = reservationTimeDetails?.longName ?? '';
     const shortName = reservationTimeDetails?.shortName ?? '';
-    if (hideAirportCode || !shortName) {
+    if (hideShortCode || !shortName) {
         return longName;
     }
     return longName ? `${longName} (${shortName})` : `(${shortName})`;
@@ -563,7 +563,7 @@ export {
     getReservationsFromTripReport,
     getTripTotal,
     getReservationDetailsFromSequence,
-    formatAirportInfo,
+    formatTransitLocationLabel,
     getPNRReservationDataFromTripReport,
     getAirReservations,
     isPnrCancelled,
diff --git a/src/pages/Travel/TrainTripDetails.tsx b/src/pages/Travel/TrainTripDetails.tsx
index 3296de4ea6f0..2c1aaeb0b27a 100644
--- a/src/pages/Travel/TrainTripDetails.tsx
+++ b/src/pages/Travel/TrainTripDetails.tsx
@@ -7,7 +7,7 @@ import UserPills from '@components/UserPills';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import DateUtils from '@libs/DateUtils';
-import {formatAirportInfo} from '@libs/TripReservationUtils';
+import {formatTransitLocationLabel} from '@libs/TripReservationUtils';
 import CONST from '@src/CONST';
 import type {PersonalDetails} from '@src/types/onyx';
 import type {Reservation} from '@src/types/onyx/Transaction';
@@ -23,7 +23,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
 
     const startDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.start.date));
     const endDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.end.date));
-    const trainRouteDescription = `${formatAirportInfo(reservation.start)} ${translate('common.conjunctionTo')} ${formatAirportInfo(reservation.end)}`;
+    const trainRouteDescription = `${formatTransitLocationLabel(reservation.start)} ${translate('common.conjunctionTo')} ${formatTransitLocationLabel(reservation.end)}`;
     const trainDuration = DateUtils.getFormattedDurationBetweenDates(translate, new Date(reservation.start.date), new Date(reservation.end.date));
 
     const displayName = personalDetails?.displayName ?? reservation.travelerPersonalInfo?.name;
@@ -49,7 +49,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
                 description={translate('travel.trainDetails.departs')}
                 descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
                 titleComponent={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{startDate.hour}</Text>}
-                helperText={formatAirportInfo(reservation.start)}
+                helperText={formatTransitLocationLabel(reservation.start)}
                 helperTextStyle={[styles.pb3, styles.mtn2]}
                 interactive={false}
             />
@@ -57,7 +57,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
                 description={translate('travel.trainDetails.arrives')}
                 descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
                 titleComponent={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{endDate.hour}</Text>}
-                helperText={formatAirportInfo(reservation.end)}
+                helperText={formatTransitLocationLabel(reservation.end)}
                 helperTextStyle={[styles.pb3, styles.mtn2]}
                 interactive={false}
             />
diff --git a/tests/unit/TripReservationUtilsTest.ts b/tests/unit/TripReservationUtilsTest.ts
index fa0867d44dc1..5af0a2f5b272 100644
--- a/tests/unit/TripReservationUtilsTest.ts
+++ b/tests/unit/TripReservationUtilsTest.ts
@@ -1,5 +1,5 @@
 /* cspell:disable */
-import {formatAirportInfo, getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
+import {formatTransitLocationLabel, getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
 import CONST from '@src/CONST';
 import type {Pnr, TripData} from '@src/types/onyx/TripData';
 import {airReservationPnrData, airReservationTravelers} from '../data/TripAirReservationData';
@@ -2720,11 +2720,11 @@ describe('TripReservationUtils', () => {
         });
 
         it('should render only longName when shortName is empty (no trailing space, no empty parens)', () => {
-            expect(formatAirportInfo({date: '', longName: 'Brockenhurst', shortName: '', cityName: ''})).toEqual('Brockenhurst');
+            expect(formatTransitLocationLabel({date: '', longName: 'Brockenhurst', shortName: '', cityName: ''})).toEqual('Brockenhurst');
         });
 
         it('should render longName with parenthesized shortName when both are present', () => {
-            expect(formatAirportInfo({date: '', longName: 'Solana Beach station', shortName: 'SOL', cityName: ''})).toEqual('Solana Beach station (SOL)');
+            expect(formatTransitLocationLabel({date: '', longName: 'Solana Beach station', shortName: 'SOL', cityName: ''})).toEqual('Solana Beach station (SOL)');
         });
 
         it('should preserve a clean station code in rail shortName', () => {
