Skip to content

[Due for payment 2026-05-07] [Due for payment 2026-05-05] [$250] "Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen #88986

Closed

Description

@MelvinBot

Clicking the "Switch to Expensify Classic" button in the Settings menu leads to a 404 page instead of navigating to the exit survey.

Slack thread: https://expensify.slack.com/archives/C07NMDKEFMH/p1777307175469079

Root Cause

EXIT_SURVEY_REASON.entryScreens in ROUTES.ts:346-349 only permits Settings_Troubleshoot. However, the "Switch to Expensify Classic" button also exists on InitialSettingsPage.tsx:340-341, which calls createDynamicRoute(EXIT_SURVEY_REASON.path) without a basePath.

On wide screens, the default central pane is Settings_Profile (SettingsSplitNavigator.tsx:40), so createDynamicRoute builds settings/profile/exit-survey/reason. getStateFromPath then resolves Settings_Profile as the focused route, which is not in entryScreens, so it logs a warning and falls through to RNGetStateFromPath which can't match the path → 404.

The same button on TroubleshootPage.tsx:145 works fine because the focused route IS Settings_Troubleshoot.

Console warning: [getStateFromPath.ts][DynamicRoute] Focused route Settings_Profile is not allowed to access dynamic route with suffix exit-survey/reason - ""

Fix

Add SCREENS.SETTINGS.PROFILE.ROOT to EXIT_SURVEY_REASON.entryScreens in ROUTES.ts.

Related

Issue OwnerCurrent Issue Owner: @parasharrajat
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022048836662852713050
  • Upwork Job ID: 2048836662852713050
  • Last Price Increase: 2026-04-27

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    ExternalAdded to denote the issue can be worked on by a contributor
    on Apr 27, 2026
  2. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Apr 27, 2026
  3. melvin-bot commented on Apr 27, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @parasharrajat (External)

  4. melvin-bot commented on Apr 27, 2026

    @melvin-bot
  5. changed the title [-]"Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen[/-] [+][$250] "Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen[/+] on Apr 27, 2026
  6. aswin-s commented on Apr 27, 2026

    @aswin-s
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When a user clicks "Switch to Expensify Classic" from the main Settings page (InitialSettingsPage), it causes a 404 instead of opening the exit survey, which prevents the user from completing the expected switch flow from that settings entry point.

    What is the root cause of that problem?

    The issue is caused by how dynamic settings routes are validated in src/ROUTES.ts and resolved from the current settings context. createDynamicRoute() builds the exit survey URL relative to the currently focused central pane screen, and getStateFromPath only allows that route when the focused screen is listed in the route's entryScreens configuration.

    For DYNAMIC_ROUTES.EXIT_SURVEY_REASON in src/ROUTES.ts, entryScreens includes SCREENS.SETTINGS.TROUBLESHOOT but does not include SCREENS.SETTINGS.PROFILE.ROOT. On wide screens, SettingsSplitNavigator in src/libs/Navigation/AppNavigator/Navigators/SettingsSplitNavigator.tsx uses SCREENS.SETTINGS.PROFILE.ROOT as the defaultCentralScreen, so opening the survey from InitialSettingsPage resolves from Settings_Profile. Because that screen is not allowed in entryScreens, getStateFromPath rejects the route and the app falls through to an unmatched path, which results in the 404.

    The same configuration gap exists for DYNAMIC_ROUTES.EXIT_SURVEY_CONFIRM in src/ROUTES.ts, whose entryScreens also omits SCREENS.SETTINGS.PROFILE.ROOT. As a result, the direct confirm-step path fails in the same way when InitialSettingsPage navigates there without first opening the reason step.

    What changes do you think we should make in order to solve the problem?

    1. src/ROUTES.ts — add SCREENS.SETTINGS.PROFILE.ROOT to EXIT_SURVEY_REASON.entryScreens.
    2. src/ROUTES.ts — add SCREENS.SETTINGS.PROFILE.ROOT to EXIT_SURVEY_CONFIRM.entryScreens.

    What alternative solutions did you explore? (Optional)

    Passing an explicit basePath of settings/troubleshoot to createDynamicRoute from InitialSettingsPage would force the route to resolve through the Troubleshoot screen. This was rejected because it hard-codes the main Settings page to a sibling screen's path and makes the flow depend on that URL structure instead of the route's own allowed entry screens.

  7. MelvinBot commented on Apr 27, 2026

    @MelvinBot
    ContributorAuthor

    Proposal

    What is the root cause of that problem?

    EXIT_SURVEY_REASON.entryScreens only permits Settings_Troubleshoot. On wide screens, the sidebar (InitialSettingsPage) and a central pane are rendered together. The default central pane is Settings_Profile (SettingsSplitNavigator.tsx:40).

    When the user clicks "Switch to Expensify Classic" from the sidebar (InitialSettingsPage.tsx:340), createDynamicRoute builds the path relative to the current central pane (e.g. /settings/profile/exit-survey/reason). getStateFromPath then extracts Settings_Profile as the focused route, finds it's not in entryScreens, and the route falls through to a 404.

    What changes do you think we should make in order to solve the problem?

    Add all settings central pane screens to EXIT_SURVEY_REASON.entryScreens in src/ROUTES.ts:346-349. The sidebar button can be clicked while any central pane is focused (not just Profile), so we need to allow all of them. The same fix should also be applied to EXIT_SURVEY_CONFIRM at line 350-353, since InitialSettingsPage can navigate directly to it as well (line 344).

    Alternatively, if every settings screen should be able to reach the exit survey, using the wildcard '*' in entryScreens would be the simplest and most future-proof approach.

    What alternative solutions did you explore? (Optional)

    Adding only SCREENS.SETTINGS.PROFILE.ROOT (as suggested in the issue body) fixes the default case but leaves the bug reproducible when the user has navigated to any other central pane (Preferences, Wallet, Security, etc.) before clicking the sidebar button.


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.

  8. wildan-m commented on Apr 27, 2026

    @wildan-m
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On wide layouts, clicking the Switch to Expensify Classic button on the Settings page (the entry shown in the general menu of InitialSettingsPage) navigates to a 404 page instead of opening the exit-survey reason step.

    The same button on the Troubleshoot page works correctly, so the bug is specific to the entry point on the main Settings screen.

    What is the root cause of that problem?

    The handler that runs when the button is clicked builds the destination URL with createDynamicRoute:

    resetExitSurveyForm(() => {
    if (shouldOpenSurveyReasonPage) {
    Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.EXIT_SURVEY_REASON.path));
    return;
    }
    Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.EXIT_SURVEY_CONFIRM.path));
    });

    createDynamicRoute is called without an explicit basePath, so it defaults to Navigation.getActiveRoute() and concatenates the dynamic suffix to whatever URL is currently active:

    const createDynamicRoute = (dynamicRouteSuffixWithParams: string, basePath?: string): Route => {
    const [suffixPath] = splitPathAndQuery(dynamicRouteSuffixWithParams);
    if (!suffixPath || !isDynamicRouteSuffix(suffixPath)) {
    throw new Error(`The route name ${suffixPath} is not supported in createDynamicRoute`);
    }
    const routePath = basePath ?? Navigation.getActiveRoute();
    return combinePathAndSuffix(routePath, dynamicRouteSuffixWithParams);
    };

    On wide layouts, the central pane of SettingsSplitNavigator defaults to Settings_Profile, so the active URL when the user is on Settings is /settings/profile:

    defaultCentralScreen={SCREENS.SETTINGS.PROFILE.ROOT}

    The combined target therefore becomes /settings/profile/exit-survey/reason. When getStateFromPath resolves that URL, the focused route is Settings_Profile, but the EXIT_SURVEY_REASON dynamic route only allows Settings_Troubleshoot as an entry screen:

    App/src/ROUTES.ts

    Lines 346 to 353 in 626a3df

    EXIT_SURVEY_REASON: {
    path: 'exit-survey/reason',
    entryScreens: [SCREENS.SETTINGS.TROUBLESHOOT],
    },
    EXIT_SURVEY_CONFIRM: {
    path: 'exit-survey/confirm',
    entryScreens: [SCREENS.SETTINGS.DYNAMIC_EXIT_SURVEY_REASON, SCREENS.SETTINGS.TROUBLESHOOT],
    },

    Because Settings_Profile is not in entryScreens, the dynamic resolver logs a warning (Focused route Settings_Profile is not allowed to access dynamic route with suffix exit-survey/reason) and falls back to RNGetStateFromPath, which has no static match for settings/profile/exit-survey/reason and renders the 404 NotFoundPage.

    The Troubleshoot version works because the user is already focused on Settings_Troubleshoot at the moment of the click, which is in the allow-list.

    The same handler in InitialSettingsPage also has a second branch that navigates straight to EXIT_SURVEY_CONFIRM when the classic-redirect prompt was previously dismissed (shouldOpenSurveyReasonPage === false), and EXIT_SURVEY_CONFIRM.entryScreens has the same gap — it allows Dynamic_ExitSurvey_Reason and Settings_Troubleshoot but not Settings_Profile, so that branch 404s too once a user reaches it.

    What changes do you think we should make in order to solve the problem?

    Add SCREENS.SETTINGS.PROFILE.ROOT to the entryScreens allow-list of both EXIT_SURVEY_REASON and EXIT_SURVEY_CONFIRM in src/ROUTES.ts. Both branches of the handler in InitialSettingsPage need to work, so both dynamic routes need the same entry screen registered.

    After this change, when the button on the main Settings page builds /settings/profile/exit-survey/reason (or .../exit-survey/confirm), the dynamic resolver will accept Settings_Profile as a valid focused route, and getStateFromPath will return the correct state to render the exit-survey screen.

    The Troubleshoot button is unaffected — Settings_Troubleshoot remains in the allow-list, so its behavior does not change.

    Compare branch:
    https://github.com/Expensify/App/compare/main...wildan-m:App:wildan/88986-exit-survey-profile-entry-screen?expand=1

    What alternative solutions did you explore? (Optional)

    • Pass an explicit basePath such as ROUTES.SETTINGS into createDynamicRoute from the InitialSettingsPage handler so the resolved URL is always /settings/exit-survey/reason regardless of which central pane is active. This works but it requires changing call-site logic, and createDynamicRoute's allow-list mechanism is the intended place to declare which screens may launch a dynamic route — so configuring entryScreens is more in line with existing conventions.
    • Make EXIT_SURVEY_REASON.entryScreens accept the wildcard ['*'] as KEYBOARD_SHORTCUTS does. Rejected because it would silently widen the allow-list to every screen in the app and lose the safety net the explicit list is intended to provide.
  9. mjasikowski commented on Apr 27, 2026

    @mjasikowski
    Contributor

    @MelvinBot implement your proposal in a draft PR

  10. Guidondor commented on Apr 27, 2026

    @Guidondor

    Proposal

    Root cause: Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.EXIT_SURVEY_REASON.path)) in InitialSettingsPage.tsx (lines 340–341) doesn't specify a base path, so createDynamicRoute resolves to the currently focused route. On wide screens the default central pane is Settings_Profile, which is not listed in EXIT_SURVEY_REASON.entryScreens in ROUTES.ts (lines 346–349) — causing the 404.

    Fix: Add SCREENS.SETTINGS.PROFILE.ROOT to entryScreens for both EXIT_SURVEY_REASON and EXIT_SURVEY_CONFIRM in ROUTES.ts:

    EXIT_SURVEY_REASON: {
        path: 'exit-survey/reason',
        entryScreens: [SCREENS.SETTINGS.TROUBLESHOOT, SCREENS.SETTINGS.PROFILE.ROOT],
    },
    EXIT_SURVEY_CONFIRM: {
        path: 'exit-survey/confirm',
        entryScreens: [SCREENS.SETTINGS.DYNAMIC_EXIT_SURVEY_REASON, SCREENS.SETTINGS.TROUBLESHOOT, SCREENS.SETTINGS.PROFILE.ROOT],
    },
    
    This mirrors the working behavior in TroubleshootPage.tsx and allows the exit survey flow to be triggered from InitialSettingsPage on wide screens without breaking existing behavior.
  11. MelvinBot commented on Apr 27, 2026

    @MelvinBot
    ContributorAuthor

    🤖 Draft PR created: #88990
    Please mark it as "Ready for review" when it is ready for review.

  12. removed
    Help WantedApply this label when an issue is open to proposals by contributors
    on Apr 27, 2026
  13. 4 remaining items

  14. youssef-lr commented on Apr 28, 2026

    @youssef-lr
    Contributor

    Tested well for me, when I manually change the url to anything + exit-survey/reason
    Image

  15. changed the title [-][$250] "Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen[/-] [+][Due for payment 2026-05-05] [$250] "Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen[/+] on Apr 28, 2026
  16. melvin-bot commented on Apr 28, 2026

    @melvin-bot

    Reviewing label has been removed, please complete the "BugZero Checklist".

  17. melvin-bot commented on Apr 28, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.63-0 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-05-05. 🎊

  18. melvin-bot commented on Apr 28, 2026

    @melvin-bot

    @mjasikowski / @youssef-lr The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

  19. melvin-bot commented on Apr 30, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.64-31 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-05-07. 🎊

  20. changed the title [-][Due for payment 2026-05-05] [$250] "Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen[/-] [+][Due for payment 2026-05-07] [Due for payment 2026-05-05] [$250] "Switch to Expensify Classic" button on Settings page leads to 404 due to missing entryScreen[/+] on Apr 30, 2026
  21. melvin-bot commented on Apr 30, 2026

    @melvin-bot

    @mjasikowski / @youssef-lr The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions