App icon indicating copy to clipboard operation
App copied to clipboard

[$250] Accounting reporting tabs (Statements/Unapproved card) aren't updating in real time

Open m-natarajan opened this issue 1 month ago • 9 comments

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: Reproducible in staging?: Needs Reproduction (Unable to reproduce) Reproducible in production?: Needs Reproduction If this was caught during regression testing, add the test name, ID and link from BrowserStack: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by: @miljakljajic Slack conversation (hyperlinked to channel name): #Convert

Action Performed:

  1. Set up a card feed on a workspace and assign a card to a user
  2. Add a category to several card transactions
  3. Ensure there are unapproved transactions showing in Unapproved card
  4. Update the category on the previously categorised transactions via Reports > Reports
  5. Attempt to edit the category again via Reports > Unapproved card

Expected Result:

Updating a transaction’s category from the Reports tab should immediately reflect in the Unapproved Card tab. Both tabs should display consistent real-time data.

Actual Result:

Trying to edit the category from the Unapproved card tab gives a "hmm...its not here" error. The category change does not update in the Unapproved Card tab until cache cleared

Workaround:

Unknown

Platforms:

Select the officially supported platforms where the issue was reproduced:

  • [ ] Android: App
  • [ ] Android: mWeb Chrome
  • [ ] iOS: App
  • [ ] iOS: mWeb Safari
  • [ ] iOS: mWeb Chrome
  • [ ] Windows: Chrome
  • [x] MacOS: Chrome / Safari
  • [ ] MacOS: Desktop
Platforms Tested: On which of our officially supported platforms was this issue tested:
  • [ ] Android: App
  • [ ] Android: mWeb Chrome
  • [ ] iOS: App
  • [ ] iOS: mWeb Safari
  • [ ] iOS: mWeb Chrome
  • [x] Windows: Chrome
  • [ ] MacOS: Chrome / Safari
  • [ ] MacOS: Desktop

Screenshots/Videos

Add any screenshot/video evidence

The screenshots and Onyx data for the issue is in OP

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022000742154667381866
  • Upwork Job ID: 2000742154667381866
  • Last Price Increase: 2025-12-23
Issue OwnerCurrent Issue Owner: @rojiphil

m-natarajan avatar Dec 11 '25 14:12 m-natarajan

Triggered auto assignment to @brianlee-expensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

melvin-bot[bot] avatar Dec 11 '25 14:12 melvin-bot[bot]

verifying in slack whether this should be treated as an external issue

brianlee-expensify avatar Dec 16 '25 01:12 brianlee-expensify

Job added to Upwork: https://www.upwork.com/jobs/~022000742154667381866

melvin-bot[bot] avatar Dec 16 '25 01:12 melvin-bot[bot]

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

melvin-bot[bot] avatar Dec 16 '25 01:12 melvin-bot[bot]

🚨 Edited by proposal-police: This proposal was edited at 2025-12-30 07:14:58 UTC.

Proposal

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

When a user edits an expense from the Search interface (for eg Reports tab, **Unapproved card** tab, or Statements tab) and then navigates to a different search tab, the app displays "Hmm... it's not here" error page instead of showing the expense details.

Besides that , transaction data (category, amount...etc.) becomes stale and does not update in real-time when switching between different search tabs.

What is the root cause of that problem?

The root cause is missing snapshot synchronization for REPORT_ACTIONS and REPORT collections during expense updates in getUpdateMoneyRequestParams.

Snapshot system:

When viewing search results, the app operates in a "snapshot context" where useOnyx reads from ONYXKEYS.COLLECTION.SNAPSHOT${hash} instead of main Onyx collections. This is controlled by:

  1. CONST.SEARCH.SNAPSHOT_ONYX_KEYS defines which collections are redirected to snapshot:
    • ONYXKEYS.COLLECTION.REPORT
    • ONYXKEYS.COLLECTION.REPORT_ACTIONS
    • ONYXKEYS.COLLECTION.TRANSACTION
    • ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS
    • (and others)

https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/CONST/index.ts#L7102-L7110

  1. The custom useOnyx hook redirects these keys to snapshot when isOnSearch=true:

https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/hooks/useOnyx.ts#L50-L78

  1. Critical: SearchScopeProvider sets isOnSearch=true and only wraps the SearchList component inside Search/index.tsx .

https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/components/Search/index.tsx#L1026-L1098

Edit pages are separate navigation routes and are NOT wrapped in SearchScopeProvider, meaning they read from main Onyx collections.

Why the "not found" error occurs:

The bug manifests through many flow , one of them for eg:

  1. User visits Tab A (e.g: "Unapproved card") - Snapshot A is populated with search results including REPORT_ACTIONS and REPORT data
  2. User visits Tab B (e.g: "Reports") - Snapshot B is populated
  3. User edits an expense category in Tab B:
    • Main Onyx collections are updated correctly
    • Snapshot B is updated with TRANSACTION_VIOLATIONS only
    • Snapshot A is NOT updated with REPORT_ACTIONS or REPORT changes
  4. User returns to Tab A:
    • If cached Snapshot A is used before a new search API completes, it has stale data
    • Components within SearchScopeProvider (like SearchList) read stale REPORT_ACTIONS/REPORT
  5. User attempts to view/edit the same expense from Tab A:
    • The useShowNotFoundPageInIOUStep hook reads reportAction and iouReport
    • While edit pages normally read from main Onyx (because they're outside SearchScopeProvider), there can be timing issues during navigation where stale snapshot data affects the render

The gap in getUpdateMoneyRequestParams:

Currently, getUpdateMoneyRequestParams syncs TRANSACTION_VIOLATIONS to the snapshot:

https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/libs/actions/IOU/index.ts#L4953-L4973

However, it does NOT sync:

  • REPORT_ACTIONS (the transaction thread's report actions): https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/libs/actions/IOU/index.ts#L4621-L4627

  • REPORT (the iouReport, updated at ) :

https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/libs/actions/IOU/index.ts#L4712-L4722

This inconsistency means edits made from one search tab don't propagate to other cached search snapshots.

Specific reproduction conditions:

Based on the issue report, the bug specifically requires:

  • Company card transactions with a card feed assigned to a user
  • Pre-cached snapshot data in the "Unapproved card" tab
  • Editing via "Reports > Reports" tab, then attempting to edit via "Reports > Unapproved card"

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

Modify getUpdateMoneyRequestParams in src/libs/actions/IOU/index.ts to add explicit snapshot synchronization for REPORT_ACTIONS and REPORT, following the existing pattern used for TRANSACTION_VIOLATIONS.

1. Sync REPORT_ACTIONS to snapshot (after line 4627):

When hash and updatedReportAction exist, sync the updated report action to the snapshot:

if (hash && updatedReportAction && transactionThread?.reportID) {
    // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
    optimisticData.push({
        onyxMethod: Onyx.METHOD.MERGE,
        key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
        value: {
            data: {
                [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`]: {
                    [updatedReportAction.reportActionID]: updatedReportAction,
                },
            },
        },
    });
}

2. Sync iouReport to snapshot (after line 4722):

When hash exists, sync the updated iouReport to the snapshot:

if (hash && iouReport?.reportID) {
    // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
    optimisticData.push({
        onyxMethod: Onyx.METHOD.MERGE,
        key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`,
        value: {
            data: {
                [`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`]: {
                    ...updatedMoneyRequestReport,
                    ...(isTotalIndeterminate && {pendingFields: {total: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}),
                },
            },
        },
    });
}

3. Add corresponding failureData rollbacks for both collections to maintain consistency on API failure.

Each sync block should include:

  • optimisticData: Immediate UI update with pending state
  • failureData: Rollback with original data on API failure
  • Defensive null guards: Check for hash, reportID, and relevant data existence before syncing

This follows the established pattern used for TRANSACTION_VIOLATIONS sync in :

https://github.com/Expensify/App/blob/1c1ade94876825a4ecef626275bb46b708c25f5c/src/libs/actions/IOU/index.ts#L4953-L4973

What alternative solutions did you explore? (Optional)

  1. Modifying useOnyx hook to always fall back to main collection: Rejected because it would defeat the purpose of snapshot isolation during search operations and could cause data inconsistency.

  2. Forcing search refresh after edit: Rejected because it degrades UX with unnecessary loading states and API calls.

  3. Syncing TRANSACTION to snapshot: While TRANSACTION is in SNAPSHOT_ONYX_KEYS, the main issue is the missing REPORT_ACTIONS and REPORT sync. However, adding TRANSACTION sync would provide more complete real-time updates for category/amount changes across tabs.


mavrickdeveloper avatar Dec 18 '25 06:12 mavrickdeveloper

@rojiphil Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

melvin-bot[bot] avatar Dec 20 '25 00:12 melvin-bot[bot]

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

melvin-bot[bot] avatar Dec 23 '25 16:12 melvin-bot[bot]

@rojiphil 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

melvin-bot[bot] avatar Dec 24 '25 00:12 melvin-bot[bot]

@rojiphil @brianlee-expensify this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!

melvin-bot[bot] avatar Dec 25 '25 21:12 melvin-bot[bot]

@rojiphil 8 days overdue is a lot. Should this be a Weekly issue? If so, feel free to change it!

melvin-bot[bot] avatar Dec 26 '25 00:12 melvin-bot[bot]

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

melvin-bot[bot] avatar Dec 30 '25 16:12 melvin-bot[bot]

@m-natarajan Is this still reproducible? I cannot

https://github.com/user-attachments/assets/807dac6a-4756-4e44-b890-4c8e03c27cca

rojiphil avatar Dec 31 '25 02:12 rojiphil