Skip to content

[Due for payment 2026-11-10] [$175] [Exploratory] Navigation - App keeps on loading when opening expense from email #100682

Open

Description

@applause-bot

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.74-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/72925117/folder/54013402/41237079/1738376027
Email or phone of affected tester (no customers): emady.syed1234+bhangra@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Realme C21Y / Android 11
App Component: Chat Report View

Action Performed:

Precondition: Signed out of New Expensify.

  1. Put the device in offline mode.
  2. Open a magic link carrying exitTo, either by tapping an action link in an Expensify email or with: https://new.expensify.com/v/<accountID>/123456?exitTo=/r/<reportID>
  3. Wait 30 seconds.

Expected Result:

An offline message, the sign in page, or at minimum "This is taking longer than expected..." with a Go back button after 10 seconds.

Actual Result:

A bare centered spinner on an empty screen. No header, no error, no Go back button. The app stays unusable until the network returns.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

bug.mov

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7254670

Issue OwnerCurrent Issue Owner: @mkhutornyi
Upwork Automation - Do Not Edit

Activity

  1. applause-bot commented on Sep 9, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/b238bf30ac9b96db288b490494d0973d6cd7880c0c3924630f7a83f345eada0d

  2. changed the title [-][Exploratory] Navigation- App keeps on loading when opening expense from email[/-] [+][Exploratory] Navigation - App keeps on loading when opening expense from email[/+] on Sep 9, 2026
  3. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 24, 2026
  4. MelvinBot commented on Sep 24, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The expense report screen has a loading gate with no exit condition. It shows a skeleton whenever an expense report has a non-zero total but zero transactions in Onyx — with no timeout, no retry, and no error state.

    src/libs/MoneyRequestReportUtils.ts:163:

    const isStillLoadingData = transactions?.length === 0 && ((isReportLoadPending && !reportLoadingState?.hasOnceLoadedReportActions) || report?.total !== 0 || hasPendingDismissWrite);

    The report?.total !== 0 term is not tied to any in-flight request. Once it's true, the only way out is transactions arriving, the user going offline, or the total becoming 0.

    That exactly matches the repro. The WS owner already has the report row (with its total) from OpenApp, but not its transactions — those only arrive with OpenReport. Tapping the email link resumes the backgrounded app straight into /r/<reportID>. If that OpenReport never fires or its response never lands, src/pages/inbox/ReportActions.tsx:72 returns the skeleton forever. The report in this repro has two transactions, so total !== 0 is guaranteed.

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

    1. Make the gate request-driven, not total-driven. In shouldWaitForTransactions, only treat "no transactions yet" as loading while an OpenReport for that report is actually pending. Drop report?.total !== 0 as a standalone reason to wait, or pair it with the loading flag the way the first term already is.
    2. Add a bounded fallback. If transactions still haven't arrived after the request settles, render the report with an empty/error state rather than an indefinite skeleton. The same gate is used by src/components/MoneyRequestReportView/MoneyRequestReportView.tsx:156-158, so both call sites are covered by fixing the util.

    Regression coverage should assert that an expense report with total !== 0 and no transactions stops showing the skeleton once its OpenReport has resolved.

    Investigation details, alternatives considered, and what would confirm this

    Confidence: medium. The unbounded gate is confirmed in code and was byte-identical in the reported build (9.4.74-0 = 40f2e907). What is not confirmed is whether OpenReport fails to fire on the warm deep-link path or fires and returns without transactions.

    What would settle it: check whether an OpenReport request for that reportID was sent from the affected account right after the tap.

    • No request → the warm deep link never reached the fetch effect, and the navigation path is also at fault.
    • Request returned 200 → the response carried no transactions (or Onyx never merged them), and the gate above is the sole latch.

    There is also a [OpenReportStall] alert at src/pages/inbox/ReportFetchHandler.tsx:501-505 that fires after 30s when the loading flag never clears. Note it will not fire for this case if OpenReport succeeded — its absence is itself a signal.

    Alternatives ruled out:

    Separate defect found along the way (not this bug, worth its own issue): setInitialURL in src/components/InitialURLContextProvider.tsx:40-47 has no external callers, so initialURL is a mount-time-only snapshot that is never refreshed for a warm onNewIntent link. Combined with src/libs/SessionUtils.ts:11-41, where isLoggingInAsNewUser(undefined, email) returns true, a stale snapshot can sign a valid session out. The symptom there is a sign-out, not a hang, so it isn't this issue.

    Possibly related issues:


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/100682) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  5. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 24, 2026
  6. melvin-bot commented on Sep 24, 2026

    @melvin-bot

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

  7. melvin-bot commented on Sep 24, 2026

    @melvin-bot

    Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.

  8. 30 remaining items

  9. changed the title [-][Exploratory] Navigation - App keeps on loading when opening expense from email[/-] [+][$175] [Exploratory] Navigation - App keeps on loading when opening expense from email[/+] on Nov 2, 2026
  10. melvin-bot commented on Nov 2, 2026

    @melvin-bot
  11. melvin-bot commented on Nov 2, 2026

    @melvin-bot

    Triggered auto assignment to @AndrewGable, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

  12. Aleksandr-Zykov commented on Nov 2, 2026

    @Aleksandr-Zykov

    Proposal

    What is the root cause of the problem?

    The offline repro no longer goes anywhere near the expense-report skeleton — it hangs earlier, on the magic-link sign-in page itself.

    On native, ValidateLoginPage (src/pages/ValidateLoginPage/index.tsx) is a deep-link entry point that renders nothing but <FullScreenLoadingIndicator shouldUseGoBackButton={false} />. When the user is signed out, it calls signInWithValidateCodeAndNavigate(accountID, validateCode, preferredLocale, exitTo), which does two things:

    1. signInWithValidateCode(...) applies optimistic Onyx data (session.autoAuthState = SIGNING_IN, account.isLoading = true) and sends API.write(SIGN_IN_USER_WITH_LINK). Offline, that write is persisted to the offline queue and is not sent — so neither its successData (autoAuthState: JUST_SIGNED_IN) nor its failureData (autoAuthState: FAILED) ever lands. The page's only escape hatch — the useEffect that navigates back when autoAuthState === FAILED — therefore never fires.
    2. Because the link carries exitTo, handleExitToNavigation(exitTo) is called, which awaits waitForUserSignIn() — an unbounded promise that resolves only when session.authToken becomes truthy. Offline, it never does.

    Result: autoAuthState is stuck at SIGNING_IN, authToken never arrives, waitForUserSignIn() never resolves, and the page keeps rendering a bare centered spinner with no header, no error and no Go back button until connectivity returns. That matches the reported actual result exactly (empty screen, no header/error/Go back, unusable until the network is back).

    Notes:

    • The hang is specific to links carrying exitTo. Without exitTo, signInWithValidateCodeAndNavigate calls Navigation.goBack() immediately, so the user lands on the sign-in page even offline.
    • The loading gate from the earlier proposal on this thread (shouldWaitForTransactions in MoneyRequestReportUtils.ts) is not involved in this repro on current main: it short-circuits with if (isOffline) return false, and the original online repro no longer occurs.

    What is the expected behavior?

    Per the issue: an offline message, the sign-in page, or at minimum a "This is taking longer than expected..." message with a Go back button after 10 seconds — instead of an indefinite bare spinner.

    What is your proposed solution?

    Wrap the page's loader in the existing FullPageOfflineBlockingView, on both platforms:

    src/pages/ValidateLoginPage/index.tsx (native)

    return (
        <FullPageOfflineBlockingView>
            {/* No "Go Back" button: this is a deep-link entry point, so there is usually no history to pop back to. */}
            <FullScreenLoadingIndicator shouldUseGoBackButton={false} />
        </FullPageOfflineBlockingView>
    );

    src/pages/ValidateLoginPage/index.web.tsx (web) — same wrap around the FullScreenLoadingIndicator rendered while autoAuthState === SIGNING_IN / isCompletingDirectSignIn.

    Behavior:

    • While offline, FullPageOfflineBlockingView swaps the bare spinner for the standard blocking view ("You appear to be offline" / "This feature requires internet"), so the user can see why the page is stuck — the first expected outcome in the issue.
    • When the network returns, FullPageOfflineBlockingView swaps back to the loader, the queued SigninUserWithLink request is sent, sign-in completes, and the already-registered handleExitToNavigation(exitTo) takes the user to their expense report — the deep-link intent is preserved with no extra code.
    • The shouldUseGoBackButton={false} opt-out is deliberately kept, matching the review decision in PR Enable the Go Back button by default on FullScreenLoadingIndicator #99786 (a "Go back" from a cold deep-link entry point would be dead).

    Alternative considered and rejected

    Bailing out to the sign-in page when offline while autoAuthState === SIGNING_IN (an effect calling Navigation.goBack(), which lands on the sign-in page via the existing resetToAppRoot fallback). This also fixes the hang, but it navigates the user away mid-flow: they lose the "it just continues when connectivity returns" behavior, and the queued sign-in completing later can yank them out of whatever they started doing on the sign-in page. The offline blocking view keeps the user in place and the flow self-resumes.

    Test plan

    Unit tests:

    1. tests/ui/LoadingIndicatorGoBackOptOutTest.tsx (native page): with the device offline, a magic link with exitTo renders the offline message instead of a bare ActivityIndicator, and still offers no "Go back"; when the network returns, the loader is back (still with no "Go back").
    2. tests/ui/ValidateLoginPageTest.tsx (web page): offline + exitTo + autoAuthState: SIGNING_IN shows common.youAppearToBeOffline and no validate-login-loading; restoring connectivity brings the loader back.

    Manual tests:

    1. [Native Android/iOS] Sign out → go offline → open https://new.expensify.com/v/<accountID>/<code>?exitTo=/r/<reportID> → offline message is shown instead of the bare spinner.
    2. [Native Android/iOS] Same, then restore connectivity → sign-in completes → app lands on the report from exitTo.
    3. [Native] Signed in, offline → open the same link → still navigates to exitTo (no offline view blocking a signed-in user's handoff).
    4. [Web] Repeat 1–2 in a browser.
    5. [All platforms] Online regression: magic link with exitTo still signs in and lands on the report; magic link without exitTo still returns to the sign-in page.

  13. melvin-bot commented on Nov 2, 2026

    @melvin-bot

    📣 @Aleksandr-Zykov! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  14. Aleksandr-Zykov commented on Nov 2, 2026

    @Aleksandr-Zykov

    Contributor details
    Your Expensify account email: zich200877@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0149d8dbd138411f3d

  15. melvin-bot commented on Nov 2, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  16. changed the title [-][$175] [Exploratory] Navigation - App keeps on loading when opening expense from email[/-] [+][Due for payment 2026-11-10] [$175] [Exploratory] Navigation - App keeps on loading when opening expense from email[/+] on Nov 3, 2026
  17. melvin-bot commented on Nov 3, 2026

    @melvin-bot

    @mkhutornyi

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.30-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-11-10. 🎊

    The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ 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]. If no checklist is needed for this issue, you can click: [no checklist 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 contributorNot a priority

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions