Skip to content

[Due for payment 2026-06-30] [$250] LHN - Self DM disappears from LHN while distance expense is loading. #92552

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 9.3.98-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: #92351
Email or phone of affected tester (no customers): [email protected]
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Motorola MotoG60 / Android 12 (Hybrid app) - Windows 11 / Chrome
App Component: Left Hand Navigation (LHN)

Action Performed:

  1. Open the staging.new.expensify.com website.
  2. Sign up with a new account and select "Track expenses for my business" option.
  3. Navigate to "Inbox"
  4. Create a map distance expense via FAB and submit it to "Self DM"
  5. Once you land on Self DM, navigate to a different chat. (Or navigate to "Inbox" on Android app)
  6. Note that after navigating out of chat, Self DM disappears from LHN while loading is still visible.
  7. Note that after loading finishes, Self DM reappears on LHN.

Expected Result:

After submitting a track distance expense to Self DM, chat should remain visible on LHN while expense is still loading.

Actual Result:

After submitting a map distance expense to Self DM, chat disappears from LHN until expense finishes loading.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7170636_1780501338878.Track_Map.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7170636

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

Activity

  1. applause-bot commented on Jun 3, 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/fa24c1dd492a2c7b131e77bcc276ada4f2c75a4b90f6d9803d0848baa8cbdb87

  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Jun 3, 2026
  3. MelvinBot commented on Jun 3, 2026

    @MelvinBot
    Contributor

    Proposal

    Confidence: Medium. The LHN visibility mechanism below is verified from code; the exact field that flips during the loading window needs one runtime confirmation (called out at the end).

    What is the root cause of that problem?

    While you are viewing the Self DM, it stays in the LHN only because reasonForReportToBeInOptionList short-circuits and returns IS_FOCUSED for the currently-open report (ReportUtils.ts:9579). The instant you navigate away, the Self DM must qualify for the LHN on its own merits, and the LHN's incremental updater deletes any report that fails the check (SidebarUtils.ts:512-514).

    There are two different visibility checks, and they disagree about Self DMs:

    • The canonical check reasonForReportToBeInOptionList is designed to always keep a Self DM in the LHN (the LHN passes includeSelfDM: true, so it returns IS_SELF_DM at ReportUtils.ts:9725).
    • But an earlier gate in shouldDisplayReportInLHN runs first and bails out with shouldDisplay: false when isHidden && !shouldOverrideHidden (SidebarUtils.ts:327-339). shouldOverrideHidden does not exempt Self DMs, and requiresAttention is always false for a Self DM track expense (no outstanding child request / no IOU report to approve — getReasonAndReportActionThatRequiresAttention returns null, ReportUtils.ts:4210-4359).

    So the Self DM survives the early gate only while its notification preference reads as a visible value (a fresh Self DM is MUTE). getReportNotificationPreference defaults to HIDDEN whenever the current user's participant entry / preference is missing or empty (ReportUtils.ts:1897-1903). During the distance-loading window the Self DM's participants[currentUser].notificationPreference is momentarily read as empty/HIDDEN, so the early gate hides it; once the route finishes loading the participant data is restored to MUTE and the Self DM reappears. This only bites a brand-new account because that's when the Self DM is freshly created with incomplete participant data.

    Why it's only visible while open, and why distance (not regular) expenses trigger it
    • A regular expense's amount is known immediately, but a map distance expense has amount: 0 until the backend computes the route, so the transaction carries pendingFields.waypoints: 'add' for the whole loading window. The LHN re-evaluates the Self DM on navigation during this window.
    • The Self DM's lastMessageText is optimistically set to "tracking $0.00" (non-empty), so a plain "empty report" check is not the cause — confirming the bug is in the visibility gate, not the empty-chat path.
    • requiresAttention/isEmpty come from the async-derived REPORT_ATTRIBUTES value (reportAttributes.ts); the transaction update on load-completion re-triggers this recompute, which is what re-adds the Self DM.

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

    Make the early hidden-gate in shouldDisplayReportInLHN consistent with the canonical reasonForReportToBeInOptionList: a Self DM (with a visible preference per the LHN's includeSelfDM/isSelfDMWithVisiblePreference logic) should never be dropped by the isHidden && !shouldOverrideHidden short-circuit. Concretely, add isSelfDM(report) to the shouldOverrideHidden conditions at SidebarUtils.ts:327-335 so the canonical check at ReportUtils.ts:9725 makes the final call. This keeps the Self DM stable in the LHN regardless of transient participant/preference data during loading.

    What alternative solutions did you explore? (Optional)

    Hardening the optimistic/server data so the Self DM's participants[currentUser].notificationPreference is never transiently empty during a track-expense request. This addresses the trigger rather than the symptom, but it's broader and harder to scope, and the LHN should be resilient to incomplete data regardless.

    Runtime confirmation still needed

    Confirm via Onyx state (or the attached video repro) that during the loading window report.participants[currentUserAccountID].notificationPreference on the Self DM is empty/hidden (so isHiddenForCurrentUser returns true) and is restored to mute after the route loads. If the participant data is actually intact the whole time, the disappearance would instead point to a derived-value timing issue in REPORT_ATTRIBUTES, and the fix would target that recompute ordering instead.


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

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Jun 3, 2026
  5. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Jun 3, 2026
  6. yusufdeveloper2903 commented on Jun 3, 2026

    @yusufdeveloper2903
    Contributor

    🚨 Edited by proposal-police: This proposal was edited at 2026-06-03 17:01:49 UTC.

    Proposal

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

    After tracking a map distance expense to the Self DM, the Self DM row vanishes from the LHN while the expense is still loading (the distance route is being fetched), and only reappears once loading finishes. The chat should stay in the LHN the whole time.

    What is the root cause of that problem?

    There is exactly one branch in reasonForReportToBeInOptionList that can drop a Self DM from the LHN — the "exclude empty chats" guard:

    https://github.com/Expensify/App/blob/8020a9338e8/src/libs/ReportUtils.ts#L9709-L9727

    const isSelfDMWithVisiblePreference = isSelfDM(report) && !isHiddenForCurrentUser(report);
    
    if (
        excludeEmptyChats &&
        isEmptyChat &&
        isChatReport(report) &&
        !isPolicyExpenseChat(report) &&
        !isTripRoom(report) &&
        !isConciergeChatReport(report) &&
        !isSystemChatReport &&
        !isSelfDMWithVisiblePreference &&
        canHideReport
    ) {
        return null;          // ← Self DM removed here
    }
    
    if (isSelfDMReport) {
        return includeSelfDM ? CONST.REPORT_IN_LHN_REASONS.IS_SELF_DM : null;   // ← otherwise always shown
    }

    If the code reaches L9725, a Self DM is always returned (IS_SELF_DM). So the only way it disappears is by hitting the return null at L9722, which requires both:

    1. isEmptyChat === true, and
    2. !isSelfDMWithVisiblePreference — i.e. the Self DM's notification preference resolves to hidden. For a freshly-onboarded account the participant has no explicit preference, and getReportNotificationPreference falls back to HIDDEN:
      https://github.com/Expensify/App/blob/8020a9338e8/src/libs/ReportUtils.ts#L1900-L1902

    So the Self DM's presence in the LHN hangs entirely on isEmptyChat being false. And isEmptyChat comes from isEmptyReport, which reads the cached reportAttributes[reportID].isEmpty derived value:

    https://github.com/Expensify/App/blob/8020a9338e8/src/libs/ReportUtils.ts#L8876-L8881

    That cached value is produced by generateIsEmptyReport, whose entire notion of "empty" is the last-message text — it has no awareness that the report actually contains an expense:

    https://github.com/Expensify/App/blob/8020a9338e8/src/libs/ReportUtils.ts#L9023-L9034

    if (report.lastMessageText) {
        return false;
    }
    const lastVisibleMessage = getLastVisibleMessage(report.reportID, isReportArchived);
    return !lastVisibleMessage.lastMessageText;

    A brand-new Self DM is created with lastMessageText: undefined (https://github.com/Expensify/App/blob/8020a9338e8/src/libs/ReportUtils.ts#L6577). While a distance expense is loading, its transaction still carries pendingFields.waypoints (isFetchingWaypointsFromServer — https://github.com/Expensify/App/blob/8020a9338e8/src/libs/TransactionUtils/index.ts#L1057-L1059) and the merchant is the Pending… placeholder. During that window the Self DM's derived isEmpty attribute (recomputed off the report/action/transaction churn — https://github.com/Expensify/App/blob/8020a9338e8/src/libs/actions/OnyxDerived/configs/reportAttributes.ts#L361) transiently resolves back to true, so the guard above removes the row; once the route resolves and the attribute settles, isEmpty flips to false and the row returns.

    Why it's distance-specific: distance is the only track-to-Self-DM flow that has a meaningful server round-trip (route computation) keeping the report in the pending/loading state. For a manual track the optimistic data is complete instantly, so the empty re-evaluation window is too short to see. The underlying fragility is the same for any flow: a Self DM with a hidden preference depends on a last-message-derived isEmpty flag that doesn't know the report already holds an expense.

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

    A Self DM that already contains an expense is never genuinely empty, so it should not be subject to the empty-chat guard — regardless of its notification preference or a transient isEmpty value. I'd broaden isSelfDMWithVisiblePreference to also keep a Self DM that owns a tracked expense (a one-transaction thread). currentReportActions, chatReport and getOneTransactionThreadReportID are already in scope in this function (the latter is used a few lines up at L9696):

    - const isSelfDMWithVisiblePreference = isSelfDM(report) && !isHiddenForCurrentUser(report);
    + // A Self DM that already holds an expense (e.g. a tracked expense whose distance route is still
    + // loading) is not genuinely empty. Keep it in the LHN regardless of notification preference, so it
    + // doesn't flicker out while `isEmpty` is transiently true during the pending/loading window.
    + const selfDMHasExpense = isSelfDMReport && !!getOneTransactionThreadReportID(report, chatReport, currentReportActions);
    + const isSelfDMWithVisiblePreference = isSelfDMReport && (!isHiddenForCurrentUser(report) || selfDMHasExpense);

    With selfDMHasExpense === true, !isSelfDMWithVisiblePreference is false, so the guard at L9711-L9723 can't drop the report and execution reaches the IS_SELF_DM return at L9725 — keeping the chat in the LHN for the entire loading window. The optimistic IOU action exists from the moment the expense is created, so getOneTransactionThreadReportID returns the thread throughout loading; the value is stable, so there is no flicker.

    • Offline: the optimistic transaction/IOU action are present offline, so selfDMHasExpense is true and the row stays — which is the desired offline-first behavior.
    • Truly empty Self DM: selfDMHasExpense is false, so behavior is unchanged (an empty hidden-preference Self DM is still hidden as today).
    • Platforms: the LHN filter is shared, so this fixes web, mWeb and the native/hybrid apps in one place.

    What alternative solutions did you explore? (Optional)

    • Make generateIsEmptyReport consider transactions. Correct in spirit, but it runs for every report in a hot derived path and would need transaction lookups; scoping the fix to the Self DM gate is cheaper and lower-risk.
    • Force the Self DM's notification preference to a visible value when tracking. This masks the symptom by side-stepping isSelfDMWithVisiblePreference, but it changes user-visible notification behavior and still leaves the isEmpty flicker for any other hidden-preference path — wrong layer to fix it.
    • Persist a non-empty lastMessageText harder in the optimistic data. The optimistic write already sets it (https://github.com/Expensify/App/blob/8020a9338e8/src/libs/actions/IOU/TrackExpense.ts#L262, https://github.com/Expensify/App/blob/8020a9338e8/src/libs/actions/IOU/TrackExpense.ts#L295); the disappearance is the cached isEmpty attribute briefly going stale during the loading churn, so chasing the last-message value is fragile. Keying visibility off "the report owns an expense" is the robust invariant.
  7. David-2002065 commented on Jun 3, 2026

    @David-2002065
    Contributor

    Proposal

    What is the root cause of that problem?

    The Self DM is being filtered out by the "exclude empty chats" branch in reasonForReportToBeInOptionList, here:

    https://github.com/Expensify/App/blob/main/src/libs/ReportUtils.ts#L9709-L9727

    const isSelfDMWithVisiblePreference = isSelfDM(report) && !isHiddenForCurrentUser(report);
    
    if (
        excludeEmptyChats &&
        isEmptyChat &&
        isChatReport(report) &&
        !isPolicyExpenseChat(report) &&
        !isTripRoom(report) &&
        !isConciergeChatReport(report) &&
        !isSystemChatReport &&
        !isSelfDMWithVisiblePreference &&   // ← Self DM only escapes when it ALSO has a non-hidden preference
        canHideReport
    ) {
        return null;
    }
    
    if (isSelfDMReport) {
        return includeSelfDM ? CONST.REPORT_IN_LHN_REASONS.IS_SELF_DM : null;
    }

    Two things line up to make Self DM disappear during the distance-loading window:

    1. isSelfDMWithVisiblePreference is false for this user. A fresh "Track expenses for my business" signup lands with a Self DM whose participant notification preference is HIDDEN (or unset) — isHiddenForCurrentUser treats unset/empty/HIDDEN as hidden — https://github.com/Expensify/App/blob/main/src/libs/ReportUtils.ts#L2241-L2250. So the Self-DM exception in the filter doesn't activate. (The Self DM is only kept visible up to this point because it's the current report — that check fires earlier at L9579 — so the user doesn't notice until they navigate away.)

    2. isEmptyChat is briefly true during the map-distance loading. isEmptyReport reads from the cached reportAttributesDerivedValue first — https://github.com/Expensify/App/blob/main/src/libs/ReportUtils.ts#L8876-L8888 — and only falls back to report.lastMessageText if no attributes entry exists. For the optimistic distance flow, buildOnyxDataForTrackExpense writes lastMessageText from getReportActionText(iouAction) on the chat report — https://github.com/Expensify/App/blob/main/src/libs/actions/IOU/TrackExpense.ts#L255-L269 — but the action text for an in-flight distance request resolves through a translation that returns an empty/placeholder until the waypoint route comes back from the server (pendingFields.waypoints = ADD is set on the transaction at https://github.com/Expensify/App/blob/main/src/libs/actions/IOU/TrackExpense.ts#L1067 ). So during the loading window, the IOU action text — and therefore Self DM's lastMessageText — is empty, and the derived isEmpty flips to true.

    The moment the user navigates off Self DM, the IS_FOCUSED early-return no longer applies, the filter runs, both conditions hit, and Self DM is dropped from the LHN. Once GetRoute resolves, the IOU action's text fills in, lastMessageText becomes truthy, isEmpty flips back to false, and Self DM reappears — matching the reported symptom exactly.

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

    I think the cleanest fix is to make the "include Self DM" intent explicit and unconditional at this filter, instead of routing it through the brittle isHiddenForCurrentUser check that's sensitive to optimistic-data races. Callers that pass includeSelfDM: true (the LHN path, via SidebarUtils.shouldDisplayReportInLHN at https://github.com/Expensify/App/blob/main/src/libs/SidebarUtils.ts#L341-L355 ) are asking for Self DM to be kept regardless of empty/hidden status; that's its semantics in the LHN.

    In reasonForReportToBeInOptionList:

    -    const isSelfDMWithVisiblePreference = isSelfDM(report) && !isHiddenForCurrentUser(report);
    +    const isSelfDMWithVisiblePreference = isSelfDM(report) && (includeSelfDM || !isHiddenForCurrentUser(report));

    That single line keeps Self DM visible in the LHN throughout the distance-loading window without changing behavior for the other callers (search, participant pickers) that pass includeSelfDM: false — they still get the existing visibility-preference gating.

    If we'd rather not widen the meaning of isSelfDMWithVisiblePreference, the equivalent is to hoist the Self DM short-circuit above the empty-chat block:

    +    if (isSelfDMReport && includeSelfDM) {
    +        return CONST.REPORT_IN_LHN_REASONS.IS_SELF_DM;
    +    }
    +
         if (
             excludeEmptyChats &&
             isEmptyChat &&
             ...
             !isSelfDMWithVisiblePreference &&
             canHideReport
         ) {
             return null;
         }

    Both produce the same observable outcome. I'd go with the first diff because it's a one-line change with the narrowest blast radius.

    No accompanying changes are needed in TrackExpense.ts or the reportAttributes derivation — the underlying race (an optimistic action whose text only fills in after a follow-up API call) exists for other transaction types too (scan/SmartScan), and trying to paper over it in the chat-report lastMessageText would require touching every transaction-type-specific text builder.

    Tests: the existing LHN visibility tests in tests/unit/SidebarTest.ts already cover "Self DM is shown in LHN" — I'd add one case where lastMessageText is empty AND the Self DM's notification preference is HIDDEN, asserting the report still appears when called via the LHN path (includeSelfDM: true).

    What alternative solutions did you explore? (Optional)

    • Set the Self DM's participant notificationPreference to ALWAYS optimistically inside buildOnyxDataForTrackExpense whenever a track expense is created in Self DM. Rejected — it papers over the symptom (isSelfDMWithVisiblePreference becomes true so the filter doesn't trip) but mutates a user-controlled preference as a side effect of submitting an expense, which is wrong: the user may have deliberately hidden their Self DM, and a transaction shouldn't silently un-hide it. It also doesn't help non-distance flows that hit the same race (e.g. the same disappearance can occur for SmartScan to Self DM while OCR is processing).
    • Always write a non-empty placeholder lastMessageText (e.g. translated "Distance request") into the chat report optimistically and only overwrite it once GetRoute returns. Rejected — it puts UI-string concerns into the action layer, the placeholder would briefly appear in the LHN row preview which is a worse UX than the current "no preview yet" state, and it doesn't address the underlying filter logic (the next regression in this area would rediscover the same bug).
    • Force the LHN to keep Self DM cached in currentReportsToDisplay across recomputes when there's any in-flight transaction targeting it. Rejected — couples the LHN to transaction-pending state across modules, requires plumbing a new dependency through useSidebarOrderedReports, and the bug isn't really about caching: a fresh subscriber computing the LHN from scratch during the loading window would also drop Self DM.
  8. mukhrr commented on Jun 3, 2026

    @mukhrr
    Contributor

    Proposal

    What is the root cause of that problem?

    The regression was caught after PR #92351, which only bumped react-native-onyx from 3.0.75 to 3.0.79:
    #92351

    The relevant Onyx change between those versions is mergeCollectionWithPatches becoming cache-first. That means collection subscribers now reliably see intermediate optimistic collection updates before storage finishes:
    Expensify/react-native-onyx@42b796d64f

    That exposed an existing inconsistency in our track-expense optimistic data.

    For track distance expenses to Self DM, useExpenseSubmission calls trackExpenseIOUActions:

    trackExpenseIOUActions({
    report,
    isDraftPolicy,
    action,
    existingTransaction: item,
    participantParams: {
    payeeEmail: currentUserPersonalDetails.login,
    payeeAccountID: currentUserPersonalDetails.accountID,
    participant,
    },
    policyParams: {
    policy,
    policyCategories,
    policyTagList: policyTags,
    },
    transactionParams: {
    amount: item.amount,
    distance: itemDistance,
    currency: item.currency,
    created: item.created,
    merchant: item.merchant,
    comment: item?.comment?.comment?.trim() ?? '',
    receipt: receiptFiles[item.transactionID],
    category: item.category,
    tag: item.tag,
    taxCode: transactionTaxCode,
    taxAmount: transactionTaxAmount,
    taxValue: transactionTaxValue,
    billable: item.billable,
    reimbursable: item.reimbursable,
    gpsPoint,
    validWaypoints: Object.keys(item?.comment?.waypoints ?? {}).length ? getValidWaypoints(item.comment?.waypoints, true, isGPSDistanceRequest) : undefined,
    actionableWhisperReportActionID: item.actionableWhisperReportActionID,
    linkedTrackedExpenseReportAction: item.linkedTrackedExpenseReportAction,
    linkedTrackedExpenseReportID: item.linkedTrackedExpenseReportID,
    customUnitRateID,
    attendees: item.comment?.attendees,
    isLinkedTrackedExpenseReportArchived,
    odometerStart: isOdometerDistanceRequest ? item.comment?.odometerStart : undefined,
    odometerEnd: isOdometerDistanceRequest ? item.comment?.odometerEnd : undefined,
    isFromGlobalCreate: getIsFromGlobalCreate(item),
    gpsCoordinates: isGPSDistanceRequest ? getStringifiedGPSCoordinates(gpsDraftDetails) : undefined,
    distanceRequestType,

    In buildOnyxDataForTrackExpense, we first optimistically merge the Self DM report with the new IOU preview text, but keep the old lastVisibleActionCreated:

    ...chatReport,
    lastMessageText: getReportActionText(iouAction),
    lastMessageHtml: getReportActionHtml(iouAction),
    lastReadTime: DateUtils.getDBTime(),
    // do not update iouReportID if auto submit beta is enabled and it is a scan request
    iouReportID: isASAPSubmitBetaEnabled && isScanRequest ? null : iouReport?.reportID,
    lastVisibleActionCreated: shouldCreateNewMoneyRequestReport ? reportPreviewAction?.created : chatReport.lastVisibleActionCreated,

    Then, for Self DM, we later merge the actionable whisper and update lastVisibleActionCreated:

    onyxData.optimisticData?.push({
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`,
    value: {
    lastReadTime: actionableTrackExpenseWhisper.created,
    lastVisibleActionCreated: actionableTrackExpenseWhisper.created,
    lastMessageText: CONST.ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE,

    Before the Onyx bump, this intermediate incomplete report state was easier to miss. After the cache-first Onyx change, LHN can recompute from the first Self DM report merge, where the row has new message data but stale visibility/sort metadata. So when the user navigates away while the map distance expense is still loading, LHN temporarily drops Self DM until the final server/optimistic state catches up.

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

    Make the first optimistic Self DM report merge complete. For Self DM track expenses, use the actionable whisper timestamp, or at least the IOU action timestamp, instead of preserving the old lastVisibleActionCreated.

    Small diff:

    diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts
    @@
    -                    lastVisibleActionCreated: shouldCreateNewMoneyRequestReport ? reportPreviewAction?.created : chatReport.lastVisibleActionCreated,
    +                    lastVisibleActionCreated: isSelfDMReport
    +                        ? (actionableTrackExpenseWhisper?.created ?? iouAction.created)
    +                        : shouldCreateNewMoneyRequestReport
    +                            ? reportPreviewAction?.created
    +                            : chatReport.lastVisibleActionCreated,

    I’d also add a small regression test in tests/actions/IOUTest/TrackExpenseTest.ts to verify the Self DM report is LHN-ready immediately after the optimistic track expense update:

    +it('updates selfDM lastVisibleActionCreated immediately for optimistic track expenses', async () => {
    +    const selfDMReport = {
    +        ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM),
    +        reportID: 'selfDM-distance-lhn',
    +        lastVisibleActionCreated: '2024-01-01 00:00:00.000',
    +    };
    +
    +    await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport);
    +
    +    trackExpense(getDefaultTrackExpenseParams(selfDMReport, {
    +        amount: 5000,
    +        validWaypoints: mockValidWaypoints,
    +    }));
    +    await waitForBatchedUpdates();
    +
    +    const updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`);
    +    expect(updatedReport?.lastVisibleActionCreated).not.toBe(selfDMReport.lastVisibleActionCreated);
    +});

    What alternative solutions did you explore? (Optional)

    We could change LHN to tolerate this transient Self DM state, but that would make LHN compensate for bad optimistic data. The smaller and safer fix is to make the optimistic Self DM report internally consistent before Onyx notifies subscribers.

  9. melvin-bot commented on Jun 3, 2026

    @melvin-bot

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

  10. melvin-bot commented on Jun 3, 2026

    @melvin-bot
  11. changed the title [-]LHN - Self DM disappears from LHN while distance expense is loading.[/-] [+][$250] LHN - Self DM disappears from LHN while distance expense is loading.[/+] on Jun 3, 2026
  12. github-actions commented on Jun 3, 2026

    @github-actions
    Contributor

    ⚠️ @yusufdeveloper2903 Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).

  13. 42 remaining items

  14. melvin-bot commented on Jun 29, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  15. melvin-bot commented on Jun 29, 2026

    @melvin-bot

    Payment Summary

    Upwork Job

    BugZero Checklist (@mallenexpensify)

    • I have verified the correct assignees and roles are listed above and updated the necessary manual offers
    • I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants/2062214988244884169/hired)
    • I have verified the PR was not reverted
    • I have applied any discounts due to bugs/regressions introduced by this PR
    • I have paid out the Upwork contracts or cancelled the ones that are incorrect
    • I have verified the payment summary above is correct
  16. sobitneupane commented on Jul 1, 2026

    @sobitneupane
    Contributor

    BugZero Checklist:

    • [Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.

      Link to the comment on the PR: Bump Onyx to 3.0.79 #92351 (comment)
      Link to the comment on the Issue:

    • [Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.

      Link to discussion: N/A

    • [Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.

    • [BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.

      Link to issue: https://github.com/Expensify/Expensify/issues/655600

    Regression Test Proposal

    Test:

    1. Open the app.
    2. Sign up with a new account and complete onboarding with each of these options:
      - Track expenses for my business
      - Manage my team's expenses > 11-50 employees
      - Something else
    3. Navigate to Inbox.
    4. Create a map distance expense from the FAB and submit it to Self DM.
    5. Before the distance expense finishes loading, navigate away from Self DM.
    6. Verify Self DM remains visible in the LHN while the distance expense is loading.

    Do we agree 👍 or 👎

    Zapier Logs Run ID: 00040eee-b453-a3d2-b4c5-fb756d866f94
  17. mallenexpensify commented on Jul 2, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor: @KJ21-ENG paid $250 via Upwork
    Contributor+: @sobitneupane due $250 via NewDot

    @KJ21-ENG can you please accept the job below? Please reply here and tag me once you have.
    https://www.upwork.com/jobs/~022062214988244884169

    ^ Test case created. Thx

  18. KJ21-ENG commented on Jul 2, 2026

    @KJ21-ENG
    Contributor

    @mallenexpensify Accepted, Thanks!

  19. mallenexpensify commented on Jul 2, 2026

    @mallenexpensify
    Contributor

    @KJ21-ENG paid, summary updated above. Thx.

  20. KJ21-ENG commented on Jul 2, 2026

    @KJ21-ENG
    Contributor

    @KJ21-ENG paid, summary updated above. Thx.

    Thanks!❤️

  21. JmillsExpensify commented on Jul 28, 2026

    @JmillsExpensify
    Contributor

    $250 approved for @sobitneupane

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.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions