fix: Preserve all matching selection after deselecting an expense - #95902
fix: Preserve all matching selection after deselecting an expense#95902emkhalid wants to merge 25 commits into
Conversation
|
@mkhutornyi 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] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc68c95a59
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
🤖 Reviewed the exclusion-tracking approach. The core design — keeping 1. Count vs. total may disagree for partially-excluded expense reports — worth confirming 2. Pagination offset reset is the highest-risk change — please QA carefully 3. Negative-amount (refund/credit) rows — low priority 4. Confirm the backend honors None of these are blockers on their own, but 1 and 2 are the ones I'd verify with a manual pass before merging. |
|
🤖 Code-quality pass (separate from the correctness notes above — this is about readability/maintainability, not bugs). The PR is generally clean: types are documented, the deps-omission comments were kept in sync, and the new unit tests are well-structured. A few things worth tightening: 1. Duplicated — and subtly divergent — selection-count logic (worth consolidating)
Two counters that are supposed to agree on the same selection can drift apart. Extracting one shared helper (e.g. in 2. Pagination/totals orchestration in 3. 4. Redundant per-consumer default for 5. Minor: naming drift Items 1 and 2 are the ones with real maintenance value; the rest are polish. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0b00c16ff
ℹ️ 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".
mkhutornyi
left a comment
There was a problem hiding this comment.
Please address AI review comments above
|
🤖 Regression hunt on this diff. Most of the change is correctly gated behind the new all-matching/exclusion state, so ordinary selection and pagination are largely untouched. The risks below are the spots where existing behavior changes: R1 — Group / expense-report rows now derive their checked state purely from child Normal per-item selection is fine — for non-empty groups/reports selection is keyed by child transaction ID, so R2 — R3 — Pagination offset can be pushed backward under all-matching R4 — R1 is the one I'd verify first, since it touches the existing select-all display and isn't behind the exclusion flag. |
|
🤖 Regression scan of PR #95902. These are behaviors that worked before and could break — none are confirmed at runtime (logic-derived), so I've ranked by likelihood and flagged what to test. 1. Group/report header checkbox can show unchecked under "Select all matching" (medium)
Previously both fell back to 2. Pagination offset can be pulled backward (medium) 3. Count/total no longer refreshed while paginating under all-matching (low) 4. Bulk-actions bar stays visible when all loaded rows are excluded (low) Item 1 is the one I'd verify first — it touches the most visible everyday flow (Select all in grouped/report views). |
|
@mkhutornyi I found an additional issue while testing this PR:
The backend returns Should we address this in the current PR? It appears the backend may need to return the total report count, possibly as a separate field. staging.mp4 |
|
The original scope is |
|
@emkhalid please test & compare this branch with staging/production in both online/offline mode. |
|
@mkhutornyi Thanks for clarifying that only the original issue behavior should change. My understanding is that these changes are directly required:
To clarify, This PR also changes how that existing totals request is scheduled: it avoids recalculating totals on later pages, corrects the offset when the count is missing, queues a totals request behind an in-flight non-totals request, and prevents duplicate requests during state transitions. Would you prefer that we retain these supporting request-flow changes, or restore the exact |
|
The flow change is fine but it should NOT introduce buggy behavior which QA thinks as deploy blocker. |
|
@mkhutornyi Thanks for the detailed review! I've addressed the feedback and fixed the related bugs. Could you please take another look when you have a chance? Thanks! I'll also keep testing additional cases while you review to help ensure everything is safe. |
|
@mkhutornyi I've tested as many scenarios as I could, and everything passed. When you have a chance, could you also help test for any regressions so we can avoid potential deploy blockers? Thanks! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a96e370a5
ℹ️ 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".
Review —
|
|
The original issue isn't fixed in offline mode. And Screen.Recording.2026-07-26.at.8.13.14.PM.mov |
Thanks for the review! I verified all the points:
No additional issues were found. |
Thanks for catching, done: 0-selected-fix.mov |
|
@mkhutornyi Thanks for finding and reporting these! I confirmed the offline behavior itself is expected:
I did find and fix a reconnect bug: the totals request was blocked because the search was reported as unfocused. Reconnecting now requests the authoritative totals and correctly updates the button and footer while preserving exclusions. Video demos:
offline-select-all.mp4
offline-back-online-fix.mp4 |
|
@mkhutornyi All done from my side and I am testing all the flows on platforms again, thanks |
|
Please fix conflict. Job 5 failing |
ReviewOverall this is a solid, well-tested change — the exclusion model (keep 1.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6bf8b01c46
ℹ️ 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".
| jsonQuery: exportParameters.jsonQuery, | ||
| reportIDList, | ||
| transactionIDList: selectedTransactionsKeys, | ||
| ...(isExpenseType ? {excludedTransactionIDList: Object.keys(excludedTransactions)} : {}), |
There was a problem hiding this comment.
Don’t send group keys as excluded transaction IDs
When all-matching selection is active in a grouped expense search and the user unchecks a collapsed group, excludedTransactions stores that group's group_... key because no child transaction IDs are loaded. Passing Object.keys(excludedTransactions) here as excludedTransactionIDList sends non-transaction IDs to the CSV export, so the exported file can still include the entire excluded group even though the UI count/footer subtracts it. Convert grouped exclusions to real transaction IDs or group filters before queuing the export.
Useful? React with 👍 / 👎.
|
Regression Analysis from AI: 1.
2. Grouped-Expenses group header can flash unchecked under Select-all. 3. Looks like all of them are non-blockers but please double confirm |
Thanks for the review! I verified all three points:
No additional changes are needed. |

Explanation of Change
This PR preserves the “all matching items” selection when individual expenses are unchecked.
It:
Fixed Issues
$ #94143
PROPOSAL: #94143 (comment)
Tests
Precondition: Use an account with more than 50 expenses.
Select all matching expenses and Exclude one expense
Preserve selection during pagination
Avoid recalculating totals during pagination
shouldCalculateTotals: false.Recheck an excluded expense
Exclude multiple expenses
Verify grouped and expense-report selection
Verify the selection footer
Offline tests
N/A
QA Steps
Same as Tests
// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
android-native-Select.all.matching.expenses.and.Exclude.one.expense.mov
android-native-Preserve.selection.during.pagination.mp4
Not applicable.
android-native-Recheck.an.excluded.expense.mov
android-native-Exclude.multiple.expenses.mov
android-native-Verify.grouped.and.expense-report.selection.mp4
andoir-native-Verify.the.selection.footer.mov
Android: mWeb Chrome
android-web-Select.all.matching.expenses.and.Exclude.one.expense.mov
android-web-Preserve.selection.during.pagination.mp4
Not applicable.
android-web-Recheck.an.excluded.expense.mov
android-web-Exclude.multiple.expenses.mov
android-web-Verify.grouped.and.expense-report.selection.mp4
andoir-web-Verify.the.selection.footer.mov
iOS: Native
ios-native-Select.all.matching.expenses.and.Exclude.one.expense.mov
ios-native-Preserve.selection.during.pagination.mp4
Not applicable.
ios-native-Recheck.an.excluded.expense.mov
ios-native-Exclude.multiple.expenses.mov
ios-native-Verify.grouped.and.expense-report.selection.mov
ios-native-Verify.the.selection.footer.mov
iOS: mWeb Safari
ios-web-Select.all.matching.expenses.and.Exclude.one.expense.mov
ios-web-Preserve.selection.during.pagination.mp4
Not applicable.
ios-web-Recheck.an.excluded.expense.mov
ios-web-Exclude.multiple.expenses.mov
ios-web-Verify.grouped.and.expense-report.selection.mov
ios-web-Verify.the.selection.footer.mov
MacOS: Chrome / Safari
Select.all.matching.expenses.and.Exclude.one.expense.mov
Preserve.selection.during.pagination.mov
Avoid.recalculating.totals.during.pagination.mp4
Recheck.an.excluded.expense.mov
Exclude.multiple.expenses.mov
Verify.grouped.and.expense-report.selection.webm
Verify.the.selection.footer.mov