Description
If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.
Version Number: 9.4.75-0
Reproducible in staging?: Yes
Reproducible in production?: N/A - new feature, doesn't exist in prod
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #100292
Email or phone of affected tester (no customers): josdjnodsjiosjoj@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Mac 26.5 / Chrome
App Component: Money Requests
Action Performed:
Precondition:
- Account is unverified.
- Go to staging.new.expensify.com
- Go to workspace chat.
- Create an expense.
- Open the report.
- Click Add your number on add receipt modal.
- Click on the contact email.
- Refresh the page.
Expected Result:
Inbox will remain as the background after refreshing verify contact RHP.
Actual Result:
Background changes from Inbox to Profile after refreshing verify contact RHP.
Workaround:
Unknown
Platforms:
- Android: App
- Android: mWeb Chrome
- iOS: App
- iOS: mWeb Safari
- iOS: mWeb Chrome
- Windows: Chrome
- MacOS: Chrome Safari
Screenshots/Videos
Bug7255410_1788983890883.1.mp4
Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7255410
Issue Owner
Current Issue Owner: @dmkt9Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
applause-bot commentedon Sep 9, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/548b6675253e69304754f6f5fe33b16397bcf9d31d3f487e4c559c35f5b22593
melvin-bot commentedon Sep 9, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #100292
github-actions commentedon Sep 9, 2026
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
melvin-bot commentedon Sep 9, 2026
@roryabraham @dmkt9 you have been assigned to this deploy blocker because you reviewed the offending PR: #100292
melvin-bot commentedon Sep 9, 2026
💬 A slack conversation has been started in #expensify-open-source
MelvinBot commentedon Sep 9, 2026
🔍 Investigation Summary
Classification: Frontend bug
Causing PR: #100292 - "Let the base path decide the contact methods RHP background" by @mukhrr (High confidence)
Related Issues: #100260 (the bug #100292 was fixing)
Recommendation: ROLL FORWARD
#100292 removed
DYNAMIC_CONTACT_METHODSfromSETTINGS_TO_RHPbut left the three sibling contact-method dynamic screens pinned toSettings > Profile, so the list screen now follows the base path while the details ("verify contact") screen still resolves Profile on a fresh load. The forward fix is deleting those three remaining lines from the same file, which is smaller and safer than a revert — a revert would restore the consistent-but-wrong Profile background and reintroduce #100260 plus the double-back-press bug.Assigned: @mukhrr (PR author), @roryabraham and @dmkt9 (approving reviewers) from highest-confidence causing PR only
Labels: No changes.
DeployBlockerCashis correct and stays (App-side fix, App deploy blocked).DeployBlockerwas not present, so nothing removed.📋 Detailed Analysis
Evidence
9.4.75-0).SCREENS.SETTINGS.PROFILE.DYNAMIC_CONTACT_METHODSfromSETTINGS_TO_RHP[SCREENS.SETTINGS.PROFILE.ROOT]. That map is inverted intoRHP_TO_SETTINGS, which pins a fixed fullscreen background under an RHP screen.getMatchingFullScreenRoute, theRHP_TO_SETTINGSlookup runs before the dynamic base-path branch. So any dynamic screen still listed in the relation map ignores its base path.DYNAMIC_CONTACT_METHOD_DETAILS— the "verify contact" screen — and the two new-contact-method screens are still pinned toSettings > Profile. That is exactly the "Inbox before refresh, Profile after refresh" split the reporter saw.Verification
Affected file:
src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts(the only file #100292 touched — confirmed viagh pr view 100292 --json files).The repro's "Add your number" link is built from the current screen's path, not a
/settings/...path —ReceiptAlternativeMethodsusesuseScreenBoundDynamicRoute(DYNAMIC_ROUTES.CONTACT_METHODS.path)— so from an expense report opened in the wide RHP the URL is/e/<reportID>/contact-methods, then/e/<reportID>/contact-methods/<email>/details.I ran
getAdaptedStateFromPathagainst those real URLs on current staging code and recorded which fullscreen ends up under the RHP:/e/456/contact-methodsHome/e/456/contact-methods/foo@gmail.com/detailsSettingsSplitNavigator → Settings_Profile/search/r/456/contact-methods/…/detailsSettingsSplitNavigator → Settings_Profile/e/456/contact-methods/new-contact-methodSettingsSplitNavigator → Settings_Profile/e/456/contact-methods/new/confirm-validate-codeSettingsSplitNavigator → Settings_Profile/e/456/contact-methods/verify-accountHomeSimulating the pre-#100292 map (re-adding
DYNAMIC_CONTACT_METHODS) makes both the list and the details URL resolveSettings_Profile— consistent, and matching the pre-PR behaviour described in #100260. Simulating the proposed fix (dropping the three remaining entries) gives:/e/456/contact-methods/foo@gmail.com/detailsHome✅/search/r/456/contact-methods/foo@gmail.com/detailsSearchFullscreenNavigator → Search_Root✅/r/123/contact-methods/foo@gmail.com/detailsReportsSplitNavigator → Report✅/settings/profile/contact-methods/foo@gmail.com/detailsSettingsSplitNavigator → Settings_Profile✅ (still correct)Root Cause
getMatchingFullScreenRouteresolves the fullscreen route to show under an RHP by checking the static*_TO_RHPrelation maps first, and only falls back to "strip the dynamic suffix and let the base path decide" afterwards. A dynamic route can be appended to any base path —CONTACT_METHODSdeclaresentryScreens: ['*'], andCONTACT_METHOD_DETAILS/NEW_CONTACT_METHOD/NEW_CONTACT_METHOD_CONFIRM_VALIDATE_CODEhang off it — so pinning them to a fixed central pane is always wrong.On a fresh load,
getAdaptedStatefirst triesgetDynamicRouteAdaptedState, which walks the URL suffixes down to the base path. When the base path is itself an RHP route (/e/:reportID, the expense report in the wide RHP) that walk cannot produce a fullscreen, so it falls through togetMatchingFullScreenRoute— which hits the still-presentRHP_TO_SETTINGSentry and injectsSettings > Profile. In-app navigation to the same screen never consults that map (linkToskips the fullscreen-matching block while an RHP is on top), which is why the background only jumps on refresh.Suggested fix — delete the three remaining lines so the base path decides, exactly as #100292 did for the list:
[SCREENS.SETTINGS.PROFILE.ROOT]: [ SCREENS.SETTINGS.PROFILE.DISPLAY_NAME, SCREENS.SETTINGS.PROFILE.AVATAR, - SCREENS.SETTINGS.PROFILE.DYNAMIC_CONTACT_METHOD_DETAILS, - SCREENS.SETTINGS.PROFILE.DYNAMIC_NEW_CONTACT_METHOD, - SCREENS.SETTINGS.PROFILE.DYNAMIC_NEW_CONTACT_METHOD_CONFIRM_VALIDATE_CODE, SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_SET_DEFAULT_CONFIRM,Worth a regression test asserting the resolved background for
/e/:reportID/contact-methods/:contactMethod/details, since the in-app path and the deep-link path go through different code and only the deep-link one was broken.💡 Suggested AI code review rule
This bug looks like a recurring, mechanically detectable pattern, so it may be worth a coding-standards rule so the AI reviewer catches the next one. Proposed for
Expensify/App:CONSISTENCY-18Don't pin an "anywhere" dynamic route to a fullscreen in the*_TO_RHPrelationsSCREENS.*.DYNAMIC_*screen to anysrc/libs/Navigation/linkingConfig/RELATIONS/*_TO_RHP.tsmap when itsDYNAMIC_ROUTESentry declaresentryScreens: ['*'], or when itsentryScreensare dynamic screens that themselves declare['*']. Leave it out and let the base path decide.getMatchingFullScreenRoutechecks the relation maps before the dynamic base-path branch, so a pinned dynamic screen resolves a fixed central pane on deep link / refresh while in-app navigation keeps the real base path — the background jumps on refresh (this issue).grep -rn "DYNAMIC_" src/libs/Navigation/linkingConfig/RELATIONS/, then for each hit check itsDYNAMIC_ROUTESentry insrc/ROUTES.tsforentryScreens: ['*'](directly or transitively). Five live hits today: the three contact-method screens plusDYNAMIC_EXIT_SURVEY_REASON/DYNAMIC_EXIT_SURVEY_CONFIRM, which are pinned toSettings > TroubleshootdespiteEXIT_SURVEY_*declaringentryScreens: ['*'].SETTINGS_TO_RHP.ts#L10-L12WORKSPACE_TO_RHP.ts#L28-L32The full rule file, with the ❌/✅ code inline in App's coding-standards format, would go in
Expensify/Appitself. If it's worth adding, a maintainer can open it there as a draft PR.roryabraham commentedon Sep 9, 2026
demoting this
35 remaining items
mallenexpensify commentedon Sep 29, 2026
@dmkt9 👀 above plz
dmkt9 commentedon Oct 1, 2026
@mallenexpensify Ah, yes. Sorry, I mixed this up with another issue. This issue is considered a follow-up, so no payment is due here.
mukhrr commentedon Oct 1, 2026
@dmkt9 I don't agree.
I clearly mentioned here why
So @mallenexpensify in short, #100292 fixed #100260 (which is 'Click the "Add your number" link in Expense details' case). But this issue expects "Click contact methods path" and we fixed this separately. cc @roryabraham
mallenexpensify commentedon Oct 2, 2026
@roryabraham 👀 above when you're back plz, Thx
roryabraham commentedon Oct 5, 2026
I think we agreed it's a separate issue, not a follow-up
mallenexpensify commentedon Oct 6, 2026
Payment Summary
Contributor: @mukhrr due $250 via Upwork
Contributor+: @dmkt9 paid $250 via NewDot
@dmkt9 can you please accept the job below? Please reply here and tag me once you have.
https://www.upwork.com/jobs/~022107267517493826771
dmkt9 commentedon Oct 6, 2026
@mallenexpensify Thanks. I've accepted the offer
mallenexpensify commentedon Oct 6, 2026
@dmkt9 paid, summary updated above. Thx.
flaviadefaria commentedon Oct 9, 2026
Approved: @mukhrr due $250 via NewDot