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
[$175] Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses #103339
Users can't find expenses after they are rejected or held. One customer gave an NPS score of 0 and said "expenses disappear" after being rejected. We are adding a Rejected and a Held filter to Search, but users still need to know it exists.
Solution
Add a "Rejected" and a "Held" suggested search to the Search left-hand nav.
Show it only after the user has at least one rejected or held expense.
After it first appears, keep showing it permanently, even when the user has no rejected or held expenses, since users get confused when items appear and disappear.
Note: If an entire report is rejected, the expenses in that report should also display in that filter. Any expenses that are marked as resolved should then disappear from the filter.
changed the title [-]Add a "Rejected" suggested search that appears once a user has a rejected expense[/-][+]Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses[/+]on Oct 19, 2026
changed the title [-]Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses[/-][+][$175] Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses[/+]on Oct 19, 2026
Please re-state the problem that we are trying to solve in this issue.
Users lose track of expenses after they are rejected or held. The Search left-hand nav has no "Rejected" or "Held" suggested search to help them find those expenses.
What is the root cause of that problem?
This is a new feature. CONST.SEARCH.SEARCH_KEYS has no rejected or held key. getSuggestedSearchesVisibility builds visibility only from policy and approval signals, so it can't show an item based on the user's own rejected or held expenses. There is also no stored flag that keeps a suggested search visible after it first appears.
What changes do you think we should make in order to solve the problem?
Add keys. Add REJECTED and HELD to CONST.SEARCH.SEARCH_KEYS.
Define the searches. Add both entries to getSuggestedSearches, copying the "Unapproved cash" entry:
Rejected: type:expense declined:yes, with the ThumbsDown icon that the Rejected filter chip already uses.
Held: type:expense held:yes, with the Stopwatch icon.
Add both icons to SEARCH_TYPE_MENU_ICON_NAMES. Add search.tabs.rejected / search.tabs.held labels and empty-state copy in every locale.
Detect the condition on the client. Add a useHasRejectedOrHeldExpenses hook, modeled on useHasReportAwaitingApproval. It returns two booleans:
hasRejected: any transaction where hasTransactionBeenRejected(violations) is true, or whose report matches hasReportBeenRejectedToSubmitter(report). The second check covers whole-report rejections.
hasHeld: any transaction where isOnHold(transaction) is true.
Make it sticky. Add two boolean NVPs, nvp_hasSeenRejectedSearch and nvp_hasSeenHeldSearch, following the nvp_hasSeenForYouTodo pattern. In useSearchTypeMenuSections, a useEffect calls setNameValuePair(..., true) the first time each condition is true.
Wire up visibility. Pass shouldShowRejectedSuggestion = hasRejected || hasSeenRejectedSearch (and the same for held) into createTypeMenuSections → getSuggestedSearchesVisibility. Push both items into the "Expense reports" section after Pay.
Add tests. In SearchUIUtilsTest, cover hidden by default, shown when the condition is true, and shown when only the NVP is set.
Held depends on a draft PR. The Held item needs held:yes from Add Held search filter for expenses. We can ship Rejected first and add Held when that PR merges, or put this on hold until then.
Whole-report rejections. The issue says expenses in a fully rejected report should appear under Rejected. The test steps for the Rejected filter PR suggest declined:yes may match only expenses rejected one at a time. If so, the backend matching needs an internal change. I could not verify this from the App repo.
New NVP names. An internal engineer should confirm that the backend accepts the two new NVPs through SetNameValuePair. One object NVP, such as nvp_seenSuggestedSearches: {rejected, held}, would also work.
What alternative solutions did you explore? (Optional)
Use the search results count instead of local Onyx data. This is more accurate for old data that isn't loaded locally. But it needs an extra API call on every Search page load.
Skip the NVP and always show the items once any policy exists. This is simpler, but it goes against the "only after the first rejected or held expense" requirement.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/103339) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Activity
[-]Add a "Rejected" suggested search that appears once a user has a rejected expense[/-][+]Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses[/+]melvin-bot commentedon Oct 19, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @Krishna2323 (
External)[-]Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses[/-][+][$175] Add a "Rejected" and a "Held" suggested search that appears once a user has rejected/held expenses[/+]melvin-bot commentedon Oct 19, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022112232889478822438
flaviadefaria commentedon Oct 19, 2026
@Krishna2323 do you think Melvin can work on this or should I add the
help wantedlabel?Krishna2323 commentedon Oct 19, 2026
@flaviadefaria I think I can work with Melvin, but I'll confirm once I have a proposal from him.
flaviadefaria commentedon Oct 19, 2026
Cool, want to already prompt him for the proposal then?
Krishna2323 commentedon Oct 20, 2026
@MelvinBot please post a proposal for this
MelvinBot commentedon Oct 20, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Users lose track of expenses after they are rejected or held. The Search left-hand nav has no "Rejected" or "Held" suggested search to help them find those expenses.
What is the root cause of that problem?
This is a new feature.
CONST.SEARCH.SEARCH_KEYShas no rejected or held key.getSuggestedSearchesVisibilitybuilds visibility only from policy and approval signals, so it can't show an item based on the user's own rejected or held expenses. There is also no stored flag that keeps a suggested search visible after it first appears.The filters these items will use:
declined:yesalready landed in [Payment due @QichenZhu] Add Rejected search filter for expenses and expense reports.held:yesis still in draft in Add Held search filter for expenses.What changes do you think we should make in order to solve the problem?
REJECTEDandHELDtoCONST.SEARCH.SEARCH_KEYS.getSuggestedSearches, copying the "Unapproved cash" entry:type:expense declined:yes, with theThumbsDownicon that the Rejected filter chip already uses.type:expense held:yes, with theStopwatchicon.SEARCH_TYPE_MENU_ICON_NAMES. Addsearch.tabs.rejected/search.tabs.heldlabels and empty-state copy in every locale.useHasRejectedOrHeldExpenseshook, modeled onuseHasReportAwaitingApproval. It returns two booleans:hasRejected: any transaction wherehasTransactionBeenRejected(violations)is true, or whose report matcheshasReportBeenRejectedToSubmitter(report). The second check covers whole-report rejections.hasHeld: any transaction whereisOnHold(transaction)is true.nvp_hasSeenRejectedSearchandnvp_hasSeenHeldSearch, following thenvp_hasSeenForYouTodopattern. InuseSearchTypeMenuSections, auseEffectcallssetNameValuePair(..., true)the first time each condition is true.shouldShowRejectedSuggestion = hasRejected || hasSeenRejectedSearch(and the same for held) intocreateTypeMenuSections→getSuggestedSearchesVisibility. Push both items into the "Expense reports" section after Pay.SearchUIUtilsTest, cover hidden by default, shown when the condition is true, and shown when only the NVP is set.Code references
Open questions before implementation
held:yesfrom Add Held search filter for expenses. We can ship Rejected first and add Held when that PR merges, or put this on hold until then.declined:yesmay match only expenses rejected one at a time. If so, the backend matching needs an internal change. I could not verify this from the App repo.SetNameValuePair. One object NVP, such asnvp_seenSuggestedSearches: {rejected, held}, would also work.What alternative solutions did you explore? (Optional)
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/103339)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
Krishna2323 commentedon Oct 20, 2026
@MelvinBot implement this
MelvinBot commentedon Oct 20, 2026
🤖 Draft PR created: Add a Rejected suggested search that appears once the user has a rejected expense
Please mark it as "Ready for review" or ask me to do that for you when it is ready for review.
What's in it:
type:expense declined:yes) in the Expense reports section.nvp_hasSeenRejectedSuggestedSearch, keeps the item visible after it first appears.Not included:
held:yesfilter is still a draft in Add Held search filter for expenses. Held can follow the same pattern once that PR merges.Please confirm:
nvp_hasSeenRejectedSuggestedSearch.declined:yes.Checks run
npm run typecheckSearchUIUtilsTest,SearchRouterNavigationTest, plus 9 related Search, ExportOnyxState, and search-menu suites (1,344 tests)oxfmt) couldn't run in this environment. I checked line lengths against the 190-character print width by hand.view run · no recording available