Skip to content

Stop Home refetching on first expense loads and quick tab returns - #103996

Merged
mjasikowski merged 5 commits into
Expensify:mainfrom
callstack-internal:eliran/101552-home-refresh-fixes
Oct 27, 2026
Merged

mjasikowski merged 5 commits into
Expensify:mainfrom
callstack-internal:eliran/101552-home-refresh-fixes

Conversation

@elirangoshen

@elirangoshen elirangoshen commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

This is a follow-up to #100551 (Stop RHP close from refiring every Home search). After that PR reached production, the number of Home Search calls per user did not go down. Testing locally found two more causes, and this PR fixes both.

  1. A transaction loaded for the first time counted as a change (src/libs/actions/OnyxDerived/configs/spendDataSignature.ts).
    The Home cards only know some expenses from their search snapshot. When you open one of those expenses, OpenReport writes transactions_<id> to Onyx for the first time. The signature counted that new key as a change, so every Home card refetched when the RHP closed.
    Fix: a transaction key seen for the first time now only counts as a change when it has a pending write (a local create) or the server stored it in the last 10 minutes (NEW_EXPENSE_WINDOW_MS, read from inserted). That covers a Pusher push, a statement import, or a workspace member's new expense. Opening an older expense counts nothing. The key is still recorded, so later edits to it still count.
    The "Awaiting approval" refresh key also reads each outstanding report's total (outstandingReportTotals in src/pages/home/YourSpendSection/useYourSpendData.ts). An edit from another device can reach an expense this device never loaded, which the rule above skips, but the report total still moves, so the card still refreshes.

  2. Every return to the app refetched every card (src/hooks/useTabFocusedRefresh.ts).
    Each time the browser tab became visible or the app came to the foreground, all Home cards refetched. Switching between browser tabs fired a full Home refresh every time.
    Fix: a return to the app within 60 seconds (APP_RETURN_REFRESH_INTERVAL_MS) of the card's last refresh does not refresh right away. It refreshes once when the 60 seconds end, so an expense made elsewhere while you were away still shows up. Any other refresh before then cancels it. Tab focus and data changes still refresh right away.

Measured in the browser (web dev, new account with one expense, transactions_ keys deleted from OnyxDB to simulate a cold Onyx), counting network Search calls:

Scenario Before After
Cold open of an expense from Recently added, then close the RHP 1 0
Hide/show the browser tab 3 times within ~8s 3 0 right away, 1 when the 60s window ends
Hide/show the browser tab after 60s 1 1
Quick return, then a data change before the window ends 2 1 (none when the window ends)
New expense from another account reaches this device (through ReconnectApp, since Pusher does not reach local dev) 1 1

Unit tests:

  • tests/unit/OnyxDerived/spendDataSignatureTest.ts: reset state in beforeEach; tests that relied on a first-seen bump now seed a baseline first and then edit. New cases: first load does not bump, an edit after the first load bumps, a local create bumps, a first-seen expense stored in the last 10 minutes bumps, an older one does not.
  • tests/unit/hooks/useTabFocusedRefresh.test.ts: use fake timers; app-return tests move the clock past 60s. New cases: quick returns are skipped, a data change right after a skipped return still refreshes, a skipped return refreshes once the window ends, another refresh cancels it, a return after the device clock moved back refreshes.
  • tests/unit/Search/yourSpendApplicabilityTest.ts: new cases for outstandingReportTotals: it lists only outstanding reports with their totals, and it changes when a report total changes while the report ID list stays the same.

Fixed Issues

$ #101552
PROPOSAL: N/A (follow-up to #100551)

Tests

On web:

  1. Open Home.
  2. Open DevTools > Network and filter by api/Search.
  3. In Recently added, open an expense created more than 10 minutes ago that you have not opened in this session, then close the RHP.
  4. Verify no Search request is sent.
  5. Open the same expense again and close it.
  6. Verify no Search request is sent.
  7. Switch to another browser tab (a different Chrome tab, not Inbox/Spend inside the app) and come back, a few times within a few seconds. Do this within a minute of Home loading.
  8. Verify no Search request is sent when you come back, and that one Home refresh (Search requests for the Home cards) happens about a minute after the last refresh.
  9. Wait more than one minute without leaving Home, then switch to another browser tab and back.
  10. Verify one Home refresh happens right away.
  11. Click Inbox in the app, then click Home again.
  12. Verify one Home refresh happens right away (going back to the Home tab inside the app always refreshes).
  13. Create an expense from Home.
  14. Verify Recently added updates with the new expense.
  15. Open an expense from Recently added, edit its amount, then close the RHP.
  16. Verify Recently added and the Home cards show the new amount.
  • Verify that no errors appear in the JS console

Offline tests

  1. Open Home, then go offline.
  2. Open an expense from Recently added and close the RHP.
  3. Verify no Search request is attempted.
  4. While still offline, create an expense from Home.
  5. Go back online.
  6. Verify the Home cards refresh and show the new expense.

QA Steps

  1. Sign in on a phone with an account on a paid workspace (Collect or Control) with approvals on.
  2. Create an expense on that workspace and submit its report. Wait at least 10 minutes.
  3. Open Home and note the Your spend > Awaiting approval total.
  4. In Recently added, open an expense created more than 10 minutes ago, then go back to Home.
  5. Verify the Home cards do not reload (no loading state, the list does not jump).
  6. Send the app to the background and reopen it, a few times within a few seconds.
  7. Verify the Home cards do not reload each time you reopen the app.
  8. Wait more than one minute on another app, then reopen the app.
  9. Verify the Home cards refresh once.
  10. On web, sign in to the same account and open the submitted report. Keep the phone on Home.
  11. On web, change that expense's amount (for example from $10.00 to $25.00).
  12. On the phone, stay on Home and verify Awaiting approval updates to the new total within a few seconds, without leaving Home.
  13. On the phone, create an expense from Home.
  14. Verify Recently added shows the new expense.
  15. Open an expense from Recently added, change its amount, and go back to Home.
  16. Verify Recently added and the Home cards show the new amount.
  17. On web, repeat steps 4-9, but switch to another browser tab and back instead of sending the app to the background.
  • 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 verified any copy / text that was added to the app 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)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • 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)
  • 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 the PR adds or modifies the UI:
    • I asked an AI agent to review the changes for accessibility issues and addressed its findings.
    • I tested with a screen reader (VoiceOver on macOS) and verified all new/changed elements are reachable with a logical focus order.
    • I verified all new/changed elements have meaningful accessible names and roles.
    • I verified state changes are announced (e.g. checked/unchecked, expanded/collapsed, selected).
  • 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 pr103996-1-cold-close pr103996-2-quick-tab-returns pr103996-3-tab-return-after-60s 996-1-cold-close.png…]()

Opening an expense the Home cards only know from their snapshot writes its
transaction to Onyx for the first time. spendDataSignature counted that as a
change, so closing the RHP still refetched every card. A first-seen key now
only counts when it carries a pending write, which is what a local create has.

Returning to the app also refetched every card each time, so flicking between
browser tabs fired a full Home refresh per return. A return within 60s of the
last refresh is now skipped; tab focus and data changes still refresh at once.
@elirangoshen
elirangoshen marked this pull request as ready for review October 14, 2026 14:37
@elirangoshen
elirangoshen requested review from a team as code owners October 14, 2026 14:37
@melvin-bot
melvin-bot Bot requested review from aimane-chnaif and removed request for a team October 14, 2026 14:37
@melvin-bot

melvin-bot Bot commented Oct 14, 2026

Copy link
Copy Markdown

@aimane-chnaif 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 garrettmknight and removed request for a team October 14, 2026 14:37
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-21T13:15:57.946653Z
We couldn’t complete this review. Comment @codex review to try again.
20bb5b4 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@elirangoshen

Copy link
Copy Markdown
Contributor Author

Hi @aimane-chnaif could you review it today ?

@aimane-chnaif

Copy link
Copy Markdown
Contributor

yes, starting soon

@garrettmknight
garrettmknight removed their request for review October 15, 2026 13:07
@aimane-chnaif

aimane-chnaif commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified any copy / text that was added to the app 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)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • 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 the PR adds or modifies the UI:
    • I asked an AI agent to review the changes for accessibility issues and addressed its findings.
    • I tested with a screen reader (VoiceOver on macOS) and verified all new/changed elements are reachable with a logical focus order.
    • I verified all new/changed elements have meaningful accessible names and roles.
    • I verified state changes are announced (e.g. checked/unchecked, expanded/collapsed, selected).
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari
test.mov
Screen.Recording.2026-10-22.at.7.58.29.AM.mov

@elirangoshen

Copy link
Copy Markdown
Contributor Author

Hi @aimane-chnaif any update ?

@aimane-chnaif

Copy link
Copy Markdown
Contributor

@codex review

@aimane-chnaif

Copy link
Copy Markdown
Contributor

@MelvinBot review

@aimane-chnaif

Copy link
Copy Markdown
Contributor

Looks good but let's get second pass from bots

@MelvinBot

Copy link
Copy Markdown
Contributor

Review

The logic looks correct, and I found no blocking bugs. CI passes, except Check independent approval, which is expected before approval. I found two small cleanups.

  1. Misplaced doc comment. The JSDoc for getFingerprint ("The card fields, plus what the other cards filter or group by…") now sits above the new hasPendingWrite helper. Move it back above getFingerprint, and give hasPendingWrite its own one-line comment. See spendDataSignature.ts:44-53.
  2. Test comment doesn't match the test. The comment says the expense "is then edited before Onyx is cleared", but the test clears first and then sends the edit. See spendDataSignatureTest.ts:311.
What I checked
  • Local creates still bump the counter. buildOptimisticTransaction defaults pendingAction to ADD (buildOptimisticTransaction.ts:121). CSV import sets it explicitly (ImportTransactions.ts:262). So the new first-seen skip at spendDataSignature.ts:104 doesn't swallow expenses created on this device.
  • A first-seen key is still recorded. If the user opens a snapshot-only expense and then edits or deletes it, the change bumps the counter through pendingFields or pendingAction.
  • The "Onyx cleared" test still tests something. If the baseline survived the reset, the edited amount would bump the counter to 1. The test expects 0, so it still fails if the baseline isn't dropped.
  • The throttle only affects app returns. In useTabFocusedRefresh.ts:40-45, a tab focus or a refreshKey change still refreshes right away. Every consumer puts isOffline in its key, so reconnecting still refreshes. Each card keeps its own timer, which matches the PR description.
  • Trade-off to confirm, not a bug. The PR describes this already. Expenses that arrive only through Pusher no longer refresh the Home cards while the user stays on Home. Examples are card imports, reports submitted to an approver, and expenses created on another device. The cards catch up on the next tab focus, or on an app return more than 60s after the last refresh. Make sure the issue owner accepts this.

view run

@MelvinBot

Copy link
Copy Markdown
Contributor

Regressions found

I found three regressions. None is a crash, but each leaves the Home cards stale where they used to refresh. Local creates, edits, deletes, splits, and duplicates still refresh correctly.

  1. OFX/QFX statement upload no longer updates Home. uploadOFXStatement writes no optimistic transactions. A queued job delivers them later as new keys with no pending flag, and the new first-seen rule now records those keys silently. A user who uploads a statement and goes back to Home sees no new card expenses until they switch tabs. See ImportTransactions.ts:449-499 and spendDataSignature.ts:104-107.
  2. A skipped app return is dropped, not postponed. A return less than 60s after the last refresh never sets the pending flag, and nothing re-arms it when the window ends. Combined with the Pusher trade-off, this can leave the cards stale indefinitely. Example: a user switches tabs, SmartScans a receipt on their phone, and switches back within 60s. The expense never appears while they stay on Home. The PR description says it shows up "on the next return to the app after 60 seconds", but that only holds if the user leaves the app again. See useTabFocusedRefresh.ts:40-45.
    • Suggested fix: record the skipped return and refresh once the 60s window ends (for example, with a timer), instead of discarding it.
  3. Insights for admins no longer picks up members' new expenses. Insights includes workspace members' spend. Their new expenses reach the admin through Pusher as first-seen keys, which no longer count. So the admin's chart refreshes only on their own changes, tab focus, or a return 60s or more after the last refresh. This reaches further than the card-import example in the trade-off. See useInsightData.ts:23-30.

Also: the comment "One made elsewhere arrives here" is no longer true. See useRecentlyAddedData.ts:147.

Unverified items and what I checked

Possible, not verified

  • CSV import: the optimistic keys carry pendingAction: ADD, so the import counts once. If the backend saves the transactions under different IDs than the client sent, the real keys arrive silently. That refetch may also run before the server commits. I couldn't confirm which IDs the backend uses.
  • Device clock moved backward: Date.now() - lastRefreshTime goes negative, so every return is skipped until the clock catches up.

Checked and safe

  • Local creates: request, track, distance, per diem, invoice, send money, scan, split (including new split children), and duplicate expense or report all first write the key with pendingAction: ADD. buildOptimisticTransaction sets that by default (buildOptimisticTransaction.ts:121). The only explicit null is for split children that already exist.
  • Optimistic ADD and the server's clear: these always land in separate Onyx updates, so the flag can't be cleared before the derived value runs.
  • Edit or delete after a silent first load: the key is still recorded, so the later change counts.
  • SmartScan finishing a scan this device created: it updates a known key, so it counts.
  • What triggers an app return: on web, only visibilitychange; on native, only a return from the background. The throttle doesn't affect reconnects, and isOffline is in every refresh key.
  • Sign-out, sign-in, and cold start: the baseline is rebuilt, and the account and query hashes change the keys. Nothing new depends on hasBaseline.
  • Opening an expense from the Search tab: that path writes a partial transaction, but Home refreshes on tab focus anyway. Home doesn't use that path.

view run

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@elirangoshen

Copy link
Copy Markdown
Contributor Author

@codex review

A first-seen transaction stored by the server in the last 10 minutes now
moves the spend signature, so statement imports, Pusher pushes and
members' expenses refresh the Home cards again. Opening an older expense
still costs no refetch.

A return to the app inside the 60s window is now replayed once the
window ends instead of being dropped, and a device clock that moved back
no longer blocks returns.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@elirangoshen

Copy link
Copy Markdown
Contributor Author

Regressions found

I found three regressions. None is a crash, but each leaves the Home cards stale where they used to refresh. Local creates, edits, deletes, splits, and duplicates still refresh correctly.

  1. OFX/QFX statement upload no longer updates Home. uploadOFXStatement writes no optimistic transactions. A queued job delivers them later as new keys with no pending flag, and the new first-seen rule now records those keys silently. A user who uploads a statement and goes back to Home sees no new card expenses until they switch tabs. See ImportTransactions.ts:449-499 and spendDataSignature.ts:104-107.

  2. A skipped app return is dropped, not postponed. A return less than 60s after the last refresh never sets the pending flag, and nothing re-arms it when the window ends. Combined with the Pusher trade-off, this can leave the cards stale indefinitely. Example: a user switches tabs, SmartScans a receipt on their phone, and switches back within 60s. The expense never appears while they stay on Home. The PR description says it shows up "on the next return to the app after 60 seconds", but that only holds if the user leaves the app again. See useTabFocusedRefresh.ts:40-45.

    • Suggested fix: record the skipped return and refresh once the 60s window ends (for example, with a timer), instead of discarding it.
  3. Insights for admins no longer picks up members' new expenses. Insights includes workspace members' spend. Their new expenses reach the admin through Pusher as first-seen keys, which no longer count. So the admin's chart refreshes only on their own changes, tab focus, or a return 60s or more after the last refresh. This reaches further than the card-import example in the trade-off. See useInsightData.ts:23-30.

Also: the comment "One made elsewhere arrives here" is no longer true. See useRecentlyAddedData.ts:147.

Unverified items and what I checked
Possible, not verified

  • CSV import: the optimistic keys carry pendingAction: ADD, so the import counts once. If the backend saves the transactions under different IDs than the client sent, the real keys arrive silently. That refetch may also run before the server commits. I couldn't confirm which IDs the backend uses.
  • Device clock moved backward: Date.now() - lastRefreshTime goes negative, so every return is skipped until the clock catches up.

Checked and safe

  • Local creates: request, track, distance, per diem, invoice, send money, scan, split (including new split children), and duplicate expense or report all first write the key with pendingAction: ADD. buildOptimisticTransaction sets that by default (buildOptimisticTransaction.ts:121). The only explicit null is for split children that already exist.
  • Optimistic ADD and the server's clear: these always land in separate Onyx updates, so the flag can't be cleared before the derived value runs.
  • Edit or delete after a silent first load: the key is still recorded, so the later change counts.
  • SmartScan finishing a scan this device created: it updates a known key, so it counts.
  • What triggers an app return: on web, only visibilitychange; on native, only a return from the background. The throttle doesn't affect reconnects, and isOffline is in every refresh key.
  • Sign-out, sign-in, and cold start: the baseline is rebuilt, and the account and query hashes change the keys. Nothing new depends on hasBaseline.
  • Opening an expense from the Search tab: that path writes a partial transaction, but Home refreshes on tab focus anyway. Home doesn't use that path.

view run

I addressed those in my last push

@elirangoshen

Copy link
Copy Markdown
Contributor Author

Review

The logic looks correct, and I found no blocking bugs. CI passes, except Check independent approval, which is expected before approval. I found two small cleanups.

  1. Misplaced doc comment. The JSDoc for getFingerprint ("The card fields, plus what the other cards filter or group by…") now sits above the new hasPendingWrite helper. Move it back above getFingerprint, and give hasPendingWrite its own one-line comment. See spendDataSignature.ts:44-53.
  2. Test comment doesn't match the test. The comment says the expense "is then edited before Onyx is cleared", but the test clears first and then sends the edit. See spendDataSignatureTest.ts:311.

What I checked

  • Local creates still bump the counter. buildOptimisticTransaction defaults pendingAction to ADD (buildOptimisticTransaction.ts:121). CSV import sets it explicitly (ImportTransactions.ts:262). So the new first-seen skip at spendDataSignature.ts:104 doesn't swallow expenses created on this device.
  • A first-seen key is still recorded. If the user opens a snapshot-only expense and then edits or deletes it, the change bumps the counter through pendingFields or pendingAction.
  • The "Onyx cleared" test still tests something. If the baseline survived the reset, the edited amount would bump the counter to 1. The test expects 0, so it still fails if the baseline isn't dropped.
  • The throttle only affects app returns. In useTabFocusedRefresh.ts:40-45, a tab focus or a refreshKey change still refreshes right away. Every consumer puts isOffline in its key, so reconnecting still refreshes. Each card keeps its own timer, which matches the PR description.
  • Trade-off to confirm, not a bug. The PR describes this already. Expenses that arrive only through Pusher no longer refresh the Home cards while the user stays on Home. Examples are card imports, reports submitted to an approver, and expenses created on another device. The cards catch up on the next tab focus, or on an app return more than 60s after the last refresh. Make sure the issue owner accepts this.

view run

fixed as well


// A key seen for the first time is usually a load, like opening an expense the cards only know from their
// snapshot, so only a local create or a just-stored expense counts. Recording it lets a later edit count.
if (!lastSeen && transaction && !hasPendingWrite(transaction) && !isRecentlyInserted(transaction)) {

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.

Pls. check if valid bug:

"Awaiting approval" doesn't update when an older expense is edited on another device

The Awaiting approval refresh relies on the expense counter to catch edits on outstanding reports (useYourSpendData.ts#L615-L616). An edit from another device reaches this device through Pusher. If this device never loaded that expense and it was created more than 10 minutes ago, the new first-seen rule records it without bumping the counter (spendDataSignature.ts#L114-L119). On main, the counter bumps and the total refreshes.

  1. Use an account on a paid workspace (Collect or Control) with approvals on. Have a phone and web ready.
  2. On the phone, create a $10.00 expense on that workspace and submit the report.
  3. Wait at least 10 minutes. This moves the expense's inserted time outside the new 10-minute window.
  4. On web, sign in fresh so web has never loaded that expense. Do not open the report.
  5. On web, open Home and note Your spend → Awaiting approval.
  6. On the phone, change that expense's amount to $25.00.
  7. On web, stay on Home for about 10 seconds.

Expected: Awaiting approval goes up by $15.00.
Actual: Awaiting approval keeps the old total until you switch tabs and come back.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pls. check if valid bug:

"Awaiting approval" doesn't update when an older expense is edited on another device

The Awaiting approval refresh relies on the expense counter to catch edits on outstanding reports (useYourSpendData.ts#L615-L616). An edit from another device reaches this device through Pusher. If this device never loaded that expense and it was created more than 10 minutes ago, the new first-seen rule records it without bumping the counter (spendDataSignature.ts#L114-L119). On main, the counter bumps and the total refreshes.

  1. Use an account on a paid workspace (Collect or Control) with approvals on. Have a phone and web ready.
  2. On the phone, create a $10.00 expense on that workspace and submit the report.
  3. Wait at least 10 minutes. This moves the expense's inserted time outside the new 10-minute window.
  4. On web, sign in fresh so web has never loaded that expense. Do not open the report.
  5. On web, open Home and note Your spend → Awaiting approval.
  6. On the phone, change that expense's amount to $25.00.
  7. On web, stay on Home for about 10 seconds.

Expected: Awaiting approval goes up by $15.00. Actual: Awaiting approval keeps the old total until you switch tabs and come back.

Hi I fixed it please try again

@aimane-chnaif

aimane-chnaif commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor
  1. Switch to another browser tab and back quickly, a few times in a row.
  2. Verify no Search request is sent right away, and one Home refresh happens when a minute has passed since the last refresh.

This test is failing. Am I missing anything?

(the expense was created more than 10 min ago and also waited more than a minute on Home tab)

Screen.Recording.2026-10-22.at.7.54.26.AM.mov

An edit from another device can reach an expense this device never
loaded, which the expense counter skips as a first load. The report
total still moves, so the approval refresh key now reads it.
@elirangoshen

Copy link
Copy Markdown
Contributor Author
  1. Switch to another browser tab and back quickly, a few times in a row.
  2. Verify no Search request is sent right away, and one Home refresh happens when a minute has passed since the last refresh.

This test is failing. Am I missing anything?

(the expense was created more than 10 min ago and also waited more than a minute on Home tab)

Screen.Recording.2026-10-22.at.7.54.26.AM.mov

Please try again now - I also updated the qa steps

@aimane-chnaif

Copy link
Copy Markdown
Contributor

Please merge main

@elirangoshen

Copy link
Copy Markdown
Contributor Author

Please merge main

merged

@melvin-bot
melvin-bot Bot requested a review from mjasikowski October 22, 2026 10:41
@elirangoshen

Copy link
Copy Markdown
Contributor Author

Hi @aimane-chnaif :)
Do you need anything from me to merge it ?

@aimane-chnaif

Copy link
Copy Markdown
Contributor

Hi @aimane-chnaif :) Do you need anything from me to merge it ?

No, just awaiting final review from @mjasikowski

@mjasikowski
mjasikowski merged commit 5ab0c29 into Expensify:main Oct 27, 2026
35 of 37 checks passed
@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@github-actions

Copy link
Copy Markdown
Contributor

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

@github-actions

Copy link
Copy Markdown
Contributor

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/mjasikowski in version: 9.5.24-1 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 cancelled 🔪
🍎 iOS 🍎 cancelled 🔪

@MelvinBot

Copy link
Copy Markdown
Contributor

No help site update needed: this PR only reduces background Search refetches on Home, and no article under docs/articles describes when Home cards refresh or reload.


view run

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.

5 participants