Repository navigation
Description
Expensify/Expensify Issue URL: https://github.com/Expensify/Expensify/issues/603735
Slack conversation: https://expensify.slack.com/archives/C03U7DCU4/p1771944902661119?thread_ts=1771944902.661119&cid=C03U7DCU4
Action Performed
- Open a report that has multiple expenses, where at least one expense has an RBR indicator (violation, hold, field error, receipt error, etc.)
- Look at the expense carousel in the report preview
Expected Result
The expense that requires action (has RBR) should appear first in the carousel so the user can immediately identify and act on it.
Actual Result
Expenses appear in backend insertion order. RBR-flagged expenses can be buried at position 5+ in the carousel, making them easy to miss entirely — especially since only the first 11 are shown.
Root Cause
There is no RBR-aware sorting anywhere in the transaction display path:
- Carousel —
MoneyRequestReportPreviewContent.tsx:~506doestransactions.slice(0, 11)with no sort beforehand. - Report transaction list —
MoneyRequestReportTransactionList.tsx:~325sorts by date/column only;visualOrderTransactionIDs(which drives RHP carousel navigation) inherits this non-RBR-aware order.
The data flows from Onyx derived store → Object.values() (insertion order) → carousel/list — no step prioritizes actionable items.
Proposed Fix
Sort RBR-flagged transactions to the front in both locations:
- Carousel: Sort
transactionsby RBR status before the.slice(0, 11)inMoneyRequestReportPreviewContent. - Transaction list: Use RBR status as a primary sort key in
MoneyRequestReportTransactionList, with the existing date/column sort as secondary.
Existing infrastructure to reuse
shouldShowRBRfromTransactionPreviewUtils.createTransactionPreviewConditionals()filteredViolationsalready computed per-transaction inMoneyRequestReportTransactionList(~lines 216-237)- Report-level RBR prioritization pattern in
SidebarUtils.ts:~533
Open Questions
- Should RBR-first sorting override a user-selected column sort (date, merchant, etc.) in the report view, or only apply as the default?
- Should sorting be centralized in
useReportWithTransactionsAndViolationsso all consumers benefit?
Platforms
- Android: App
- Android: mWeb Chrome
- iOS: App
- iOS: mWeb Safari
- iOS: mWeb Chrome
- Windows: Chrome
- MacOS: Chrome / Safari
Upwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022033929183187241566
- Upwork Job ID: 2033929183187241566
- Last Price Increase: 2026-03-17
Activity
melvin-bot commentedon Mar 17, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ikevin127 (
External)[-]Expense carousel doesn't show RBR-actionable expenses first[/-][+][$250] Expense carousel doesn't show RBR-actionable expenses first[/+]melvin-bot commentedon Mar 17, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022033929183187241566
MelvinBot commentedon Mar 17, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a report has multiple expenses and some have RBR indicators (violations, holds, field errors, receipt errors, etc.), the expense carousel in the report preview doesn't prioritize those actionable expenses. They appear in backend insertion order, meaning RBR-flagged expenses can be buried at position 5+ and hidden behind the "+N more" button (only the first 10 are shown as previews).
What is the root cause of that problem?
There is no RBR-aware sorting anywhere in the transaction display path:
Carousel path: Transactions are stored in Onyx as a
Record<string, Transaction>. The derived config (reportTransactionsAndViolations.ts) stores them in a plain object.useReportWithTransactionsAndViolationsretrieves them viaObject.values()(insertion order), filters out deleted ones, and passes them as a prop. AtMoneyRequestReportPreviewContent.tsx:506,transactions.slice(0, 11)takes the first 11 items (10 previews + 1 "show more" slot) with no.sort()call anywhere in the chain.Transaction list path:
MoneyRequestReportTransactionList.tsx:269-271sorts transactions by a single user-selected column (default: date ASC) usingcompareValues(). The 6 sortable columns are date, merchant, description, category, tag, and total_amount — none are violation/RBR-related.visualOrderTransactionIDs(line 325-330) inherits this non-RBR-aware order. WhilefilteredViolationsare computed per-item viauseTransactionViolations, they are only used for display and never feed back into sort order.What changes do you think we should make in order to solve the problem?
Carousel (MoneyRequestReportPreviewContent.tsx): Before the
.slice(0, 11)at line 506, sort thetransactionsarray so that RBR-flagged transactions come first. This can leverageshouldShowRBRfromTransactionPreviewUtils.createTransactionPreviewConditionals()(which OR-combines violations, field errors/hold, report violations/action errors, receipt errors, and DEW submit failures). Among RBR and non-RBR groups, preserve the existing insertion order as a stable secondary sort.Transaction list (MoneyRequestReportTransactionList.tsx): Use RBR status as a default primary sort key, with the existing column sort as secondary. This follows the same pattern used in
SidebarUtils.ts:586-613where the LHN elevates error reports to a higher priority group (group 2) above drafts and regular reports.For both locations, the violation data is already available — the carousel's parent has access to violations via
useReportWithTransactionsAndViolations, and the transaction list already computesfilteredViolationsper-item. The key question is whether RBR-first sorting should override a user-selected column sort in the transaction list or only apply as the default — this is flagged as an open question in the issue.What alternative solutions did you explore? (Optional)
useReportWithTransactionsAndViolations: This would give all consumers RBR-aware ordering automatically, but it tightly couples the hook to violation data that not all consumers may need, and it removes the ability for individual consumers to opt out.Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.abelhailefen commentedon Mar 17, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When an expense report contains multiple transactions, those requiring action (RBR indicators like violations, field errors, holds) can be buried past the 5th position in the report preview carousel and the transaction list. Because the preview only shows the first 11 items, users can completely miss actionable expenses.
What is the root cause of that problem?
There is no RBR-aware sorting in the presentation layer:
MoneyRequestReportPreviewContent.tsx, the component calls.slice(0, 11)strictly based on backend insertion order without any prior sorting.MoneyRequestReportTransactionList.tsx, transactions are sorted strictly by a standard column value (defaulting to date), completely ignoring RBR status. Since the Right-Hand Panel (RHP) carousel navigation (visualOrderTransactionIDs) is derived directly from this list, it inherits the same flawed order.What changes do you think we should make in order to solve the problem?
Create a lightweight RBR helper:
Add a minimal
transactionHasRBRutility inTransactionPreviewUtils.ts. This will evaluate transaction-level violations, holds, and receipt errors without needing heavy report/policy context.Fix the Carousel (
MoneyRequestReportPreviewContent):Before calling
.slice(0, 11), sort the transactions array using the newtransactionHasRBRhelper and the already-availablelastTransactionViolationsprop. RBR items will sort to the front, preserving insertion order as a secondary tiebreaker.Fix the Transaction List (
MoneyRequestReportTransactionList):Modify the
sortedTransactionsuseMemoto evaluate RBR status usinguseOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS).Crucial UX detail:
Apply RBR-first sorting only when the list is set to the default sort (Date / ASC). If a user explicitly clicks a column header (e.g., Merchant or Amount), we respect their choice and bypass the RBR override.
Fix the RHP Navigation:
By fixing the transaction list above,
visualOrderTransactionIDsautomatically inherits the correct RBR-first order, requiring zero extra changes.What alternative solutions did you explore? (Optional)
Always forcing RBR-first in the transaction list:
I opted against this. Overriding an explicit user action (like sorting by Amount) with RBR priority leads to broken user expectations. Applying it only to the default sort guarantees visibility on first load while keeping the table interactive.
Centralizing the sort in
useReportWithTransactionsAndViolations:While tempting, this tightly couples the hook to violation logic and forces all hook consumers to inherit an RBR-sorted array, which might cause regressions in non-UI data processors. Handling the sort at the presentation layer is safer and more isolated.
mendarb commentedon Mar 17, 2026
🚫 Duplicated proposal withdrawn by 🤖 ProposalPolice.
melvin-bot commentedon Mar 17, 2026
📣 @mendarb! 📣
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:
github-actions commentedon Mar 17, 2026
ikevin127 commentedon Mar 18, 2026
🎉 Thanks everybody for proposals!
Detailed proposals review
Here is my detailed review of the proposals based on Expensify's engineering guidelines and the current codebase logic.
Decision
Proposal 2 is the best fit for this issue and provides a much more mature and performant solution.
Detailed Code Review & Justification
1. React Code Performance & Pure Functions
shouldShowRBRfromTransactionPreviewUtils.createTransactionPreviewConditionals(). This approach is an anti-pattern for array sorting.createTransactionPreviewConditionalsis a massive function that requires 13 arguments (includingtransactionDetails,currentUserEmail,reportActions, etc.) and performs heavy string truncations and UI-layout specific mapping logic. Calling this inside a.sort()looping over potentially hundreds of transactions will severely bottleneck React render performance, causing dropped frames on mobile, especially older Android devices.transactionHasRBR). This satisfies the requirement to separate computation logic from side-effects and prevents expensive layout logic from running during a simple sort operation.When you write
transactionHasRBR, ensure it remains a pure function with strict type definitions aligned with our repository standards. Example:2. Responsiveness & User Experience
Date / ASC) is active is outstanding. Overriding an explicit user action (e.g., a user clicking the 'Amount' column header to find a specific expensive item, but getting RBR items forced to the top instead) breaks standard responsive UI expectations. Proposal 2 respects the user's intent while still solving the default visibility issue.MoneyRequestReportTransactionList.tsx, ensure the sort conditionally checks the configuration:3. State Management with Onyx
useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS)toMoneyRequestReportTransactionList.tsx. This aligns perfectly with Expensify's Onyx best practices, as we do not use the deprecatedOnyx.connectin components.selectorto only subscribe to violations relevant to thetransactionsarray passed to the component, preventing unnecessary re-renders on unrelated background validation updates.4. Edge Cases: Grouping Integration
MoneyRequestReportTransactionList.tsx, transactions can be grouped (e.g., Category, Tag) viareportLayoutGroupBy. If grouping is active,groupedTransactionsdetermines the visual order, not justsortedTransactions. You must ensure that your RBR sorting logic correctly bubbles up actionable items within their respective groups, or falls back gracefully so we don't accidentally scramble grouped layouts.5. Testing Requirements
transactionHasRBRutility to verify it catches Holds, field errors, and receipt violations correctly.MoneyRequestReportPreviewContent) modification requires an integration test to guarantee that a mocked RBR transaction correctly renders at index0ahead of older insertion-order transactions.@abelhailefen's main proposal solution LGTM as it has a clearly superior architectural and product design, see Detailed proposals review above for details on why.
Note for contributor: Please refer to the Detailed proposals review -
Actionable Feedback for PR Authornotes for guidelines during PR creation.If this is your first contribution:
🎉 Congratulations on your first contribution assignment, and seeing that it's your first, here's what's gonna happen next:
🎀👀🎀 C+ reviewed
91 remaining items
melvin-bot commentedon Jun 2, 2026
Current assignee @mallenexpensify is eligible for the Awaiting Payment assigner, not assigning anyone new.
mallenexpensify commentedon Jun 2, 2026
Payment Summary
Contributor: @abelhailefen paid $75 via Upwork
Contributor+: @ikevin127 due $250 via NewDot
@abelhailefen can you please apply to the job below? Please reply here and tag me once you have.
https://www.upwork.com/jobs/~022061936512836955018
$125 would have been price if they completed the follow-up PR. Since they didn't pay should be less so I made it $75.
grgia commentedon Jun 4, 2026
Thank you!
abelhailefen2 commentedon Jun 4, 2026
@mallenexpensify you told me to apply but my main account is banned?
melvin-bot commentedon Jun 4, 2026
📣 @abelhailefen2! 📣
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:
abelhailefen2 commentedon Jun 4, 2026
@mallenexpensify i have already applied please let me know if i should withdraw my proposal in upwork
mallenexpensify commentedon Jun 5, 2026
@abelhailefen can you please accept the job below? Please reply here and tag me once you have.
If you're not sent an offer in Upwork when you're assigned to an issue, please tag me. If an Upwork job exists, please submit a proposal.
https://www.upwork.com/jobs/~022061936512836955018
abelhailefen2 commentedon Jun 5, 2026
@mallenexpensify i have accepted the offer on upwork
mallenexpensify commentedon Jun 6, 2026
@abelhailefen paid, summary updated above. Thx.
quinthar commentedon Jun 7, 2026
Approved $250 to @ikevin127