Skip to content

[Home Page] Fix: Your spend section does not update after updating expense offline#93081

Closed
adamgrzybowski wants to merge 45 commits into
Expensify:mainfrom
software-mansion-labs:@adamgrzybowski/fix-offline-your-spend
Closed

[Home Page] Fix: Your spend section does not update after updating expense offline#93081
adamgrzybowski wants to merge 45 commits into
Expensify:mainfrom
software-mansion-labs:@adamgrzybowski/fix-offline-your-spend

Conversation

@adamgrzybowski

@adamgrzybowski adamgrzybowski commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

  • The Home "Your spend" totals (Awaiting approval and Repaid in the last 30 days) come from cached search snapshots the server only refreshes while online, so most offline money-request actions left them stale until reconnect (only amount edits were patched).
  • This change optimistically patches the affected total when an action runs offline, and rolls it back on failure. Supported actions:
    • Submit / Retract / Unapprove (Awaiting approval): add / remove / re-add the report's reimbursable total.
    • Delete / Reject (Awaiting approval, also Repaid for delete): subtract the expense's amount.
    • Cancel payment (Repaid): subtract the report's total when a reimbursement is reversed.
    • Create is handled via submit (new reports stay Open until submitted); edit amount was already supported.
  • Patches are scoped to the user's own reimbursable expenses on paid group workspaces, skipped on currency mismatch, and reconciled by the next online refresh.

Fixed Issues

$ #91370
PROPOSAL:

Tests

Offline tests

QA Steps

Prerequisites: a paid group workspace (Team/Corporate) where you are a member, and a few of your own reimbursable expenses. Each suite is run while offline (disable your network connection), starting from Home where you note the current "Your spend" totals.

Suite 1 - Awaiting approval increases

  1. Submit an Open report with your reimbursable expenses, return to Home, and verify "Awaiting approval" increases by that report's total.
  2. Unapprove an approved report you own and verify "Awaiting approval" increases by that report's total.

Suite 2 - Awaiting approval decreases

  1. Retract a submitted report and verify "Awaiting approval" decreases by that report's total.
  2. Reject a single submitted reimbursable expense and verify "Awaiting approval" decreases by that expense's amount.
  3. Delete a single submitted reimbursable expense and verify "Awaiting approval" decreases by that expense's amount.

Suite 3 - Repaid (last 30 days)

  1. Cancel a payment on a report you reimbursed within the last 30 days and verify "Repaid" decreases by that report's total.

Suite 4 - Rollback and reconciliation

  1. Perform any covered action offline and force the request to fail, then verify the "Your spend" total rolls back to its previous value.
  2. Reconnect and verify all totals reconcile with the server (no drift / no double counting).

Suite 5 - Negative checks (totals must not change)

  1. Make a currency-only edit to an expense and verify no "Your spend" total changes.
  2. Perform a covered action on a non-reimbursable expense and verify no total changes.
  3. Perform a covered action on a personal/IOU (non paid-group) expense and verify no total changes.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-06-09.at.16.05.31.mov

Patch awaiting-approval and repaid snapshot search.total aggregates optimistically when money request amounts change, since Home cannot refetch those totals while offline.
Import Your spend query builders directly from @libs/YourSpendQueryUtils
instead of a page-local re-export barrel, giving a single source of truth.
@adamgrzybowski
adamgrzybowski requested review from a team as code owners June 9, 2026 14:47
@melvin-bot
melvin-bot Bot requested review from ZhenjaHorbach and joekaufmanexpensify and removed request for a team June 9, 2026 14:47
@melvin-bot

melvin-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

@ZhenjaHorbach Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from a team and grgia and removed request for a team June 9, 2026 14:47
@melvin-bot

melvin-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

@grgia Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
...Item/MoneyRequestReportPreview/PayActionButton.tsx 96.55% <100.00%> (+0.06%) ⬆️
...m/MoneyRequestReportPreview/SubmitActionButton.tsx 97.29% <100.00%> (+0.07%) ⬆️
...uestReportPreview/useConfirmApproveReportAction.ts 100.00% <100.00%> (ø)
...c/components/ReportActionItem/MoneyRequestView.tsx 74.19% <100.00%> (+0.05%) ⬆️
...earch/SearchList/ListItem/ReportListItemHeader.tsx 91.89% <100.00%> (+0.11%) ⬆️
src/components/SettlementButton/index.tsx 82.26% <100.00%> (+0.06%) ⬆️
src/components/YourSpendPatchDataProvider.tsx 100.00% <100.00%> (ø)
src/hooks/useDeleteTransactions.ts 57.43% <100.00%> (+1.79%) ⬆️
src/hooks/useSelectionModePayment.ts 63.20% <100.00%> (+0.35%) ⬆️
src/hooks/useYourSpendPatchData.ts 100.00% <100.00%> (ø)
... and 36 more
... and 8 files with indirect coverage changes

Comment thread src/libs/actions/IOU/UpdateMoneyRequest.ts Outdated
Comment thread src/libs/actions/IOU/YourSpendSnapshotUpdate.ts
@grgia

grgia commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@adamgrzybowski lint

@joekaufmanexpensify joekaufmanexpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for product

Replace unsafe type assertions with a typed createRandomPolicy factory and
typed getSnapshotKey/buildSnapshotSearchResults helpers.
@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

@grgia fixed

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Looks like changes don't work if we reject submitted expenses
In this case, we also need to update Your spend section

2026-06-11.14.48.04.mov

@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

@ZhenjaHorbach Yeah, the question is how big the scope should be for this PR and which actions should be supported.

This issue is also mentioned here #93081 (comment)

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@ZhenjaHorbach Yeah, the question is how big the scope should be for this PR and which actions should be supported.

This issue is also mentioned here #93081 (comment)

I believe all actions should be supported, and the amount should be the same as after the response from BE

@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

@ZhenjaHorbach do you know exactly which actions?

  • create
  • delete
  • edit amount

Something else?

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@ZhenjaHorbach do you know exactly which actions?

  • create
  • delete
  • edit amount

Something else?

I suppose in the context of this flow, at least retract, unapprove, reject

@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

Actively working on this one

Scope the useOnyx selector to only the id, type, and outputCurrency
fields the snapshot builders read, so subscribers re-render only on
relevant paid-policy changes.
@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

@grgia ready for next steps

# Conflicts:
#	src/components/MoneyReportHeaderPrimaryAction/SubmitPrimaryAction.tsx
#	src/hooks/useHoldMenuSubmit.ts
#	src/libs/actions/IOU/ReportWorkflow.ts
@adamgrzybowski
adamgrzybowski force-pushed the @adamgrzybowski/fix-offline-your-spend branch from 4254814 to 5b266b1 Compare July 20, 2026 10:47
@github-actions

Copy link
Copy Markdown
Contributor

🚧 grgia has triggered a test Expensify/App build. You can view the workflow run here.

@grgia grgia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming where we landed on this

Submitting, approving, or paying a report from the Reports page while offline can leave the Home totals stale when that report's expenses were never loaded outside search.

@adamgrzybowski do we handle search entry points? It's an edge case so NAB

// to draft) so they can resubmit — matching the pre-submit-via-PDF behavior.
openPDFDownload({
onCancel: () =>
retractReport(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we pass getYourSpendPatchData() in retractReport here too?

@grgia grgia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nab

@luacmartins luacmartins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current solution feels a bit odd to me. It seems like we have all the data locally, can we just derive these values directly from the data instead of using and patching the snapshot?

An alternate solution would be to just grey out these values if any offline action is taken, but I'd confirm that approach with product first cc @trjExpensify

@trjExpensify

Copy link
Copy Markdown
Contributor

Yeah, the alt of applying the "pending update" offline treatment seems fine for this case. It signals enough that "these totals are no longer accurate in the current state" until coming back online.

@grgia

grgia commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

cc @JmillsExpensify on #93081 (comment)

@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

@grgia

@adamgrzybowski do we handle search entry points? It's an edge case so NAB

Yes

@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

@luacmartins

Can't derive locally. The totals come from server search snapshots that may include reports not in Onyx, so recomputing would undercount. That's why I patch the snapshot.

But yeah, we can just grey out the value instead of all this computation. Up to you

@JmillsExpensify

Copy link
Copy Markdown
Contributor

I don't feel strongly, so defer to @luacmartins and @trjExpensify

@luacmartins

Copy link
Copy Markdown
Contributor

I'd prefer to just grey it out and not run a bunch of complex logic that may also be incorrect given that we don't have all the data locally. I'll defer to @trjExpensify since this is mostly a product decision.

@trjExpensify

Copy link
Copy Markdown
Contributor

Cool, sounds like we're aligned that applying "pending update" to grey out the value until coming back online will suffice. 👍

@mountiny

Copy link
Copy Markdown
Contributor

Agree with simplicity and ui feedback here

@adamgrzybowski

Copy link
Copy Markdown
Contributor Author

Okay. In that case, I guess I should close this PR and create a new one with greyed out value. Can you confirm?

@mountiny

Copy link
Copy Markdown
Contributor

@grgia You fine with that as well?

@luacmartins

Copy link
Copy Markdown
Contributor

Okay. In that case, I guess I should close this PR and create a new one with greyed out value. Can you confirm?

Either that or update this PR to handle that behavior

@grgia

grgia commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Yes we can move forward with #93081 (comment) @adamgrzybowski

@grgia

grgia commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Feel free to open a new pr or update this one

@adamgrzybowski

adamgrzybowski commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@grgia the new PR is here #96966

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants