You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-10-07] [$175] Spend - Total spend footer flickers to a loading skeleton after Select all #102339
Version Number: 9.4.94-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:#98411 Email or phone of affected tester (no customers):ibellicotest+2766@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Motorola MotoG60 / Android 12 (Hybrid app) App Component: Search
Action Performed:
Prerequisite: More than 50 unreported expenses created, so Spend > Expenses has more than one page.
Open the Expensify app while online.
Navigate to "Spend" > "Expenses".
With nothing selected, tap the header checkbox and select "Select all".
Watch the total spend footer.
Expected Result:
The footer shows the correct total expenses and total spend once, and stays on it.
Actual Result:
The footer flickers. It briefly shows the correct totals, then switches to the loading skeleton, then shows the correct totals again.
When all matching items are selected, the footer only shows once the server returns a total count. Offline, that request is skipped until reconnect, so the count stays empty and the footer returns null.
"Select all on this page" leaves areAllMatchingItemsSelected false. The first clause then shows the footer with a total computed from the loaded rows.
Code references
SearchSelectionFooter.tsx:351: shouldShowFooter = (!areAllMatchingItemsSelected && selectedTransactionsKeys.length > 0) || (shouldAllowFooterTotals && !!metadata?.count). With "Select all" offline, both clauses are false, so the footer returns null at L611-L613.
Search/index.tsx:455-459: the search that calculates totals returns early while offline. It runs again on reconnect, which is why the footer appears once you're back online.
Search.ts:786-804: the normal Spend > Expenses search clears count/total in the cached snapshot, so no count is cached offline.
"Select all" already fills selectedTransactions with every loaded row, so local data is available. Only the visibility check blocks it.
SearchSelectionFooter.tsx:294: shouldUseClientTotal = !metadataCount || ... already falls back to the local total when the count is missing.
SearchBulkActionsButton.tsx:185-188 already handles this case. Offline, it falls back to the count of the loaded selected items. The footer doesn't.
What changes do you think we should make in order to solve the problem?
In SearchSelectionFooter.tsx, let the first clause of shouldShowFooter also pass when the user is offline. The rule becomes: if all matching items are selected and the app is offline, show the footer from the loaded selection, like SearchBulkActionsButton does. footerData already computes the count and total from the loaded rows (minus excluded ones) when no server count exists. Online behavior stays the same.
Also skip the loading skeleton while offline. Otherwise, a totals request that was in flight when the connection dropped can leave the footer stuck loading.
Add a unit test in tests/unit/Search/SearchSelectionFooterTest.tsx. It should set offline, set areAllMatchingItemsSelected: true, and provide loaded selections with no metadata.count. It should then check that the footer renders the loaded count and total.
What alternative solutions did you explore? (Optional)
Copy the bulk button's exact condition: show the local fallback only when all matching items are selected, no server count exists, and the app is offline or the search has errors. This is stricter and also covers search errors.
Product note: Offline, the footer shows figures for loaded rows only (for example, 50 expenses), not every matching expense. The offline footer already greys out its values, and the server totals replace them on reconnect.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/102339) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Android - Spend - Total spend footer not displayed after selecting all expenses offline[/-][+][$175] Android - Spend - Total spend footer not displayed after selecting all expenses offline[/+]on Sep 25, 2026
When a user selects "Select all on this page", areAllMatchingItemsSelected remains false and selectedTransactionsKeys.length > 0 is true. The first clause evaluates to true, displaying the client-calculated total computed by shouldUseClientTotal (line 294).
When the user selects "Select All" (matching items), areAllMatchingItemsSelected becomes true. This causes !areAllMatchingItemsSelected to evaluate to false, forcing the component to fall back to the second clause: (shouldAllowFooterTotals && !!metadata?.count).
In offline mode, the query to fetch search metadata totals cannot reach the server, leaving metadata?.countundefined. Consequently, shouldShowFooter evaluates to false and the component returns null at line 612, hiding the footer completely despite items being selected locally.
What changes do you think we should make in order to solve the problem?
In src/components/Search/SearchSelectionFooter.tsx, allow shouldShowFooter to remain active when selecting all items offline by falling back to the client-side calculated totals:
When offline, shouldUseClientTotal (line 294: !metadataCount || hasPartialSelection) already properly resolves to true because metadataCount is undefined.
At line 542, footerData correctly returns {count: selectedExpenseCount, total, currency} using the loaded offline expenses.
When back online, metadata?.count is retrieved normally from the server, preserving the standard online behavior with zero regressions.
What alternative solutions did you explore? (Optional)
We could also set an optimistic fallback on metadata.count inside SearchContext when selecting all offline, but gating it in SearchSelectionFooter.tsx is cleaner and directly aligns with the component's existing client-side totals calculation without introducing side-effects into the search state.
Test Plan:
Open Expensify and navigate to "Spend" > "Expenses".
Disconnect internet connection (go offline).
Select an expense, open the header menu, and choose "Select all on this page" -> Verify footer displays with total expenses and total spend.
Clear selection, then select an expense and choose "Select All" -> Verify footer displays seamlessly with the offline expense totals.
Reconnect internet -> Verify search metadata updates without flicker or layout shifts.
📣 @ashusatyarthi-wq! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
[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:
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:
[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.
Regression Test Proposal
Precondition:
an account with 50+ expenses (so "Select all" is offered and there is more than one page).
Test:
Go to Spend > Expenses and wait for the list to finish loading.
With nothing selected, tap the header checkbox → Select all.
Verify the footer shows the expense count and total spend, and never blanks to a loading skeleton after those values appear.
Activity
applause-bot commentedon Sep 25, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/9d58fdbd1f8ed42e187cba4f5dabc0182f0745cc7941fb9661f17f4c41817dad
MelvinBot commentedon Sep 25, 2026
Proposal
What is the root cause of that problem?
When all matching items are selected, the footer only shows once the server returns a total count. Offline, that request is skipped until reconnect, so the count stays empty and the footer returns
null."Select all on this page" leaves
areAllMatchingItemsSelectedfalse. The first clause then shows the footer with a total computed from the loaded rows.Code references
SearchSelectionFooter.tsx:351:shouldShowFooter = (!areAllMatchingItemsSelected && selectedTransactionsKeys.length > 0) || (shouldAllowFooterTotals && !!metadata?.count). With "Select all" offline, both clauses are false, so the footer returnsnullat L611-L613.Search/index.tsx:455-459: the search that calculates totals returns early while offline. It runs again on reconnect, which is why the footer appears once you're back online.Search.ts:786-804: the normal Spend > Expenses search clearscount/totalin the cached snapshot, so no count is cached offline.selectedTransactionswith every loaded row, so local data is available. Only the visibility check blocks it.SearchSelectionFooter.tsx:294:shouldUseClientTotal = !metadataCount || ...already falls back to the local total when the count is missing.SearchBulkActionsButton.tsx:185-188already handles this case. Offline, it falls back to the count of the loaded selected items. The footer doesn't.Origin: Search: show expense count and total when "Select all matching items" is active made this case depend on server totals, with no offline fallback. Before that, Hide total footer when Select All Matching Items is active hid the footer for all-matching selections on purpose.
What changes do you think we should make in order to solve the problem?
In
SearchSelectionFooter.tsx, let the first clause ofshouldShowFooteralso pass when the user is offline. The rule becomes: if all matching items are selected and the app is offline, show the footer from the loaded selection, likeSearchBulkActionsButtondoes.footerDataalready computes the count and total from the loaded rows (minus excluded ones) when no server count exists. Online behavior stays the same.Also skip the loading skeleton while offline. Otherwise, a totals request that was in flight when the connection dropped can leave the footer stuck loading.
Add a unit test in
tests/unit/Search/SearchSelectionFooterTest.tsx. It should set offline, setareAllMatchingItemsSelected: true, and provide loaded selections with nometadata.count. It should then check that the footer renders the loaded count and total.What alternative solutions did you explore? (Optional)
Copy the bulk button's exact condition: show the local fallback only when all matching items are selected, no server count exists, and the app is offline or the search has errors. This is stricter and also covers search errors.
Product note: Offline, the footer shows figures for loaded rows only (for example, 50 expenses), not every matching expense. The offline footer already greys out its values, and the server totals replace them on reconnect.
Related issue: Inconsistency when moving all unreported expenses offline during first & second bulk select is in the same area (selecting all matching items offline), but uses a different code path.
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102339)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Sep 25, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mkhutornyi (
External)[-]Android - Spend - Total spend footer not displayed after selecting all expenses offline[/-][+][$175] Android - Spend - Total spend footer not displayed after selecting all expenses offline[/+]melvin-bot commentedon Sep 25, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022103569868510055816
ashusatyarthi-wq commentedon Sep 25, 2026
Proposal
What is the root cause of that problem?
This is a regression introduced in PR #98411 ("Support 'Select all matching' when moving expenses to a report").
In
src/components/Search/SearchSelectionFooter.tsx(line 351),shouldShowFooteris guarded by:areAllMatchingItemsSelectedremainsfalseandselectedTransactionsKeys.length > 0istrue. The first clause evaluates totrue, displaying the client-calculated total computed byshouldUseClientTotal(line 294).areAllMatchingItemsSelectedbecomestrue. This causes!areAllMatchingItemsSelectedto evaluate tofalse, forcing the component to fall back to the second clause:(shouldAllowFooterTotals && !!metadata?.count).metadata?.countundefined. Consequently,shouldShowFooterevaluates tofalseand the component returnsnullat line 612, hiding the footer completely despite items being selected locally.What changes do you think we should make in order to solve the problem?
In
src/components/Search/SearchSelectionFooter.tsx, allowshouldShowFooterto remain active when selecting all items offline by falling back to the client-side calculated totals:Why this is completely safe:
shouldUseClientTotal(line 294:!metadataCount || hasPartialSelection) already properly resolves totruebecausemetadataCountis undefined.footerDatacorrectly returns{count: selectedExpenseCount, total, currency}using the loaded offline expenses.metadata?.countis retrieved normally from the server, preserving the standard online behavior with zero regressions.What alternative solutions did you explore? (Optional)
We could also set an optimistic fallback on
metadata.countinsideSearchContextwhen selecting all offline, but gating it inSearchSelectionFooter.tsxis cleaner and directly aligns with the component's existing client-side totals calculation without introducing side-effects into the search state.Test Plan:
melvin-bot commentedon Sep 25, 2026
📣 @ashusatyarthi-wq! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
ashusatyarthi-wq commentedon Sep 25, 2026
Contributor details
Your Expensify account email: ashusatyarthi@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~010e53044b5828fe60
melvin-bot commentedon Sep 25, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
fix: display total spend footer when selecting all expenses offline (E…
17 remaining items
melvin-bot commentedon Oct 3, 2026
@mkhutornyi Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
mkhutornyi commentedon Oct 4, 2026
Contributor+ 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:
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:
[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.
Regression Test Proposal
Precondition:
an account with 50+ expenses (so "Select all" is offered and there is more than one page).
Test:
Do we agree 👍 or 👎
melvin-bot commentedon Oct 6, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Oct 6, 2026
Payment Summary
Resolving PRs:
Fix the Search footer flickering to a loading skeleton after Select all #102375
Reviewer: @mkhutornyi owed $175 via NewDot
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Oct 8, 2026
Payment Summary
Contributor+: @mkhutornyi due $175 via NewDot
trjExpensify commentedon Oct 15, 2026
approved $175 for @mkhutornyi