Skip to content

[Due for payment 2026-10-05] Insights - Confirm page is not cleared when reopened from Create expense button on Insights #102066

Closed

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.92-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: #101218
Email or phone of affected tester (no customers): jsdoijnodsdisojo@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 has no expense.
  • Enable insightsPage beta.
  1. Go to staging.new.expensify,com
  2. Open FAB > Create expense > Manual.
  3. Enter amount on confirm page.
  4. Click RHP back button.
  5. Go to Insights.
  6. Click Create expense.

Expected Result:

Confirm page will be cleared when reopened from Create expense button on Insights.

Actual Result:

Confirm page is not cleared when reopened from Create expense button on Insights.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7267205_1790165322752.5.mp4

View all open jobs on GitHub

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

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 23, 2026
  2. applause-bot commented on Sep 23, 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/42d5dfd2b82b19129eaab64a5f1e503ccdd34193c5593ee5fd4d9c49b5b9353e

  3. github-actions commented on Sep 23, 2026

    @github-actions
    Contributor

    👋 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:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  4. melvin-bot commented on Sep 23, 2026

    @melvin-bot

    Triggered auto assignment to @MonilBhavsar (DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.

  5. melvin-bot commented on Sep 23, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  6. MelvinBot commented on Sep 23, 2026

    @MelvinBot
    Contributor

    🔍 Investigation Summary

    Classification: Frontend bug
    Causing PR: [NoQA] [Insights] Render the Spend dashboard (Expensify/App#101218) by mhawryluk, merged 2026-09-22 (High confidence)
    Related Issues: none

    Recommendation: ROLL FORWARD

    The bug is a one-argument omission in a brand-new, beta-gated (insightsPage) screen, so a targeted fix is far cheaper and safer than reverting a ~1.4k-line PR that the whole Insights dashboard and several follow-up Insights PRs build on.

    Assigned: mhawryluk (PR author), truph01, cristipaval, luacmartins (approving reviewers) from the highest-confidence causing PR only
    Labels: No changes. DeployBlockerCash is kept (an App PR caused this and the fix is in App). DeployBlocker was not present, so nothing was removed.

    📋 Detailed Analysis

    Evidence

    • The "Create expense" button in the Insights no-expenses state calls startMoneyRequest with undefined for the draftTransactionIDs parameter: InsightsNoExpensesState.tsx#L30
    • The FAB path, which works correctly, reads the live draft transaction IDs from Onyx and passes them through: QuickCreationActionsBar/index.tsx#L51 and #L133
    • Every other "Create expense" entry point (Search empty view, Search header create button, Getting Started section, Submit expense page, FAB menu items, Scan actions) passes draftTransactionIDs. The Insights button is the only one of them passing undefined.
    • Staging only, as reported: the Insights dashboard does not exist in production, and the whole page is gated behind the insightsPage beta (InsightsPage.tsx#L27). This matches "N/A - new feature, doesn't exist in prod".
    • Platform-independent, matching the report that Android/iOS/Chrome/Safari are all affected: the defect is in shared Onyx/action logic, not in any platform-specific file.

    Verification

    • Affected file: src/pages/Insights/states/InsightsNoExpensesState.tsx
    • Expensify/App#101218 added this file from scratch (new file mode 100644), including the exact startMoneyRequest(CONST.IOU.TYPE.CREATE, generateReportID(), undefined) line, and wired it into the dashboard (return <InsightsNoExpensesState />; in InsightsDashboard.tsx).
    • The only commit touching this path on staging is 75ad398 ("Handle empty states", 2026-09-21), which belongs to Expensify/App#101218 (merge commit fcacbdf, merged 2026-09-22T16:03:36Z). No other PR has modified it.
    • Expensify/App#101218 is on the open deploy checklist for 9.4.92-2, and the bug was reported against 9.4.92-0, so the timeline lines up.
    • The button is the only expense-creation entry point in src/pages/Insights/, so there is no second candidate within the feature.

    Root Cause

    startMoneyRequest does not clear the draft transaction itself. It delegates to clearMoneyRequest, which only nulls out the draft keys it is explicitly given:

    src/libs/actions/IOU/MoneyRequest.ts#L501-L507

    function clearMoneyRequest(transactionID: string, draftTransactionIDs: string[] | undefined, skipConfirmation = false) {
        const onyxData: Record<string, null | boolean> = {
            ...getRemoveDraftTransactionsByIDsData(draftTransactionIDs),
            [`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`]: skipConfirmation,
        };
        Onyx.multiSet(onyxData as Parameters<typeof Onyx.multiSet>[0]);
    }

    And getRemoveDraftTransactionsByIDsData short-circuits to an empty object when the list is missing:

    if (!transactionIDs?.length) {
        return {};
    }

    So when the Insights button passes undefined, the resulting Onyx.multiSet only writes the SKIP_CONFIRMATION flag and never deletes transactionDraft_<OPTIMISTIC_TRANSACTION_ID>. The flow then navigates to MONEY_REQUEST_CREATE for that same CONST.IOU.OPTIMISTIC_TRANSACTION_ID (MoneyRequest.ts#L533-L554), which rehydrates the stale draft left over from the abandoned FAB attempt, so the previously typed amount is still on the confirm page.

    Suggested fix

    In src/pages/Insights/states/InsightsNoExpensesState.tsx, read the draft IDs the same way every other entry point does and pass them to startMoneyRequest:

    const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector, canBeMissing: true});
    // ...
    buttonAction: () => startMoneyRequest(CONST.IOU.TYPE.CREATE, generateReportID(), draftTransactionIDs),

    (validTransactionDraftIDsSelector lives in src/selectors/TransactionDraft.ts#L13-L19.)

    💡 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:

    • Rule: CONSISTENCY-18 Pass live draft transaction IDs when starting an expense flow
    • Condition: Flag any call to startMoneyRequest / startDistanceRequest / clearMoneyRequest that passes a literal undefined for the draftTransactionIDs argument. Read the IDs with useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}) and pass them instead, so the previous abandoned draft is actually cleared.
    • Why: clearMoneyRequest only deletes the draft keys it is handed, and getRemoveDraftTransactionsByIDsData returns {} for an empty or missing list, so passing undefined silently skips the reset and the next entry into the flow resurrects the stale transactionDraft_<OPTIMISTIC_TRANSACTION_ID>. This exact omission caused this deploy blocker.
    • Search pattern: rg -n 'start(Money|Distance)Request\([^)]*,\s*undefined\s*,' src and rg -n 'clearMoneyRequest\([^,]+,\s*undefined' (also matches the multi-line call form when run with -U)
    • Offending code (❌):
      buttonAction: () => startMoneyRequest(CONST.IOU.TYPE.CREATE, generateReportID(), undefined),
    • Correct reference (✅):
      const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
      const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE);
      const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
      const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
      const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
      const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
      const [primaryLogin] = useOnyx(ONYXKEYS.ACCOUNT, {selector: primaryLoginSelector});
      const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS);
      const currentUserPersonalDetails = useCurrentUserPersonalDetails();
      const {getCurrencyDecimals} = useCurrencyListActions();
      const {isBetaEnabled} = usePermissions();
      const blockIfDefaultWorkspaceLacksTravel = useDefaultWorkspaceTravelGuard();
      const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
      const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? '');
      const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses();
      // scalar selector keeps useOnyx from deep-comparing thousands of policy objects
      const [defaultChatEnabledPolicySelection] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
      selector: (policies: OnyxCollection<OnyxTypes.Policy>) => getDefaultChatEnabledPolicySelection(policies, email, activePolicyID),
      });
      const defaultChatEnabledPolicyID = defaultChatEnabledPolicySelection?.defaultChatEnabledPolicyID;
      const hasMultipleChatEnabledPolicies = !!defaultChatEnabledPolicySelection?.hasMultipleChatEnabledPolicies;
      const [defaultChatEnabledPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(defaultChatEnabledPolicyID)}`);
      const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector});
      const [rules] = useOnyx(ONYXKEYS.COLLECTION.RULE);
      const shouldShowEmptyReportConfirmationForDefaultChatEnabledPolicy = useShouldShowEmptyReportConfirmation(defaultChatEnabledPolicyID);
      const travelEnabledPolicy = useMemo(
      () => Object.values(allPolicies ?? {}).find((policy) => isWorkspaceProvisionedForTravel(policy?.travelSettings) && isPolicyAccessible(policy, email ?? '')),
      [allPolicies, email],
      );
      const shouldShowBookTravel = !!travelEnabledPolicy;
      const isBlockedFromSpotnanaTravel = isBetaEnabled(CONST.BETAS.PREVENT_SPOTNANA_TRAVEL);
      const primaryContactMethod = primaryLogin ?? session?.email ?? '';
      const isTravelReady = useMemo(() => {
      if (!!isBlockedFromSpotnanaTravel || !primaryContactMethod || Str.isSMSLogin(primaryContactMethod) || !isPaidGroupPolicy(travelEnabledPolicy)) {
      return false;
      }
      return hasAcceptedTravelTerms(travelEnabledPolicy, travelSettings);
      }, [travelEnabledPolicy, isBlockedFromSpotnanaTravel, primaryContactMethod, travelSettings]);
      const handleCreateWorkspaceReport = useCallback(
      (shouldDismissEmptyReportsConfirmation?: boolean) => {
      if (!defaultChatEnabledPolicy?.id) {
      return;
      }
      const {reportID: createdReportID} = createNewReport(
      currentUserPersonalDetails,
      hasViolations,
      isASAPSubmitBetaEnabled,
      defaultChatEnabledPolicy,
      isTrackIntentUser,
      getCurrencyDecimals,
      rules,
      false,
      shouldDismissEmptyReportsConfirmation,
      );
      // Navigate to the Reports page first so getCreateReportRoute() resolves against
      // the Search/Reports fullscreen context before opening the created report modal.
      Navigation.navigate(getReportsRootRoute());
      Navigation.setNavigationActionToMicrotaskQueue(() => {
      Navigation.navigate(getCreateReportRoute({reportID: createdReportID}));
      });
      },
      [currentUserPersonalDetails, hasViolations, defaultChatEnabledPolicy, isASAPSubmitBetaEnabled, isTrackIntentUser, getCurrencyDecimals, rules],
      );
      const {openCreateReportConfirmation} = useCreateEmptyReportConfirmation({
      policyID: defaultChatEnabledPolicyID,
      policyName: defaultChatEnabledPolicy?.name ?? '',
      onConfirm: handleCreateWorkspaceReport,
      shouldHandleNavigationBack: false,
      });
      const handleExpense = useCallback(
      () =>
      interceptAnonymousUser(() => {
      startMoneyRequest(CONST.IOU.TYPE.CREATE, generateReportID(), draftTransactionIDs);

    Other call sites already match the offending shape (for example IOURequestRedirectToStartPage.tsx#L51-L57), which is why this looks generalizable rather than one-off. The full rule file, with the ❌/✅ code inline in Expensify/App's app-coding-standards format, would go in Expensify/App itself. If it's worth adding, a maintainer can open it there as a draft PR.

  7. 8 remaining items

  8. changed the title [-]Insights - Confirm page is not cleared when reopened from Create expense button on Insights[/-] [+][Due for payment 2026-10-05] Insights - Confirm page is not cleared when reopened from Create expense button on Insights[/+] on Sep 28, 2026
  9. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    @truph01

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

    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]

  10. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  11. truph01 commented on Sep 30, 2026

    @truph01
    Contributor

    This is follow-up issue, no payment is needed.

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2Engineering

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions