[$250] Search - Message preview of scan IOU in search displays [User] owes [amount] currency 0 expense
If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!
Version Number: v9.2.85-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: https://test-management.browserstack.com/projects/2219752/test-runs/TR-2467/folder/13176964/41237756/1086559176 Email or phone of affected tester (no customers): N/A Issue reported by: Applause Internal Team Bug source: Regression TC Execution Device used: Win 11/Chrome App Component: Search
Action Performed:
- Open https://staging.new.expensify.com
- Navigate to 1:1 conversation which not create expense before
- Create a scanned expense
- Click on search icon and observe the message preview of this IOU
- Back to conversation and create a manual expense
- Click on search icon and observe the message preview of this IOU
Expected Result:
The message preview of scanned IOU in search should display as: "[User] owes [amount] currency amount expense" as it works for manual expense
Actual Result:
The message preview of scanned IOU in search is displayed "[User] owes [amount] currency 0 expense"
Workaround:
Unknown
Platforms:
- [x] Android: App
- [ ] Android: mWeb Chrome
- [x] iOS: App
- [ ] iOS: mWeb Safari
- [ ] iOS: mWeb Chrome
- [x] Windows: Chrome
- [ ] MacOS: Chrome / Safari
Screenshots/Videos
https://github.com/user-attachments/assets/5e4c9ab6-8157-4810-b0c0-39a954bdd2d7
Upwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022003115447146418649
- Upwork Job ID: 2003115447146418649
- Last Price Increase: 2025-12-22
Issue Owner
Current Issue Owner: @suneox
Triggered auto assignment to @CortneyOfstad (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.
Proposal
Please re-state the problem that we are trying to solve in this issue.
Search - Message preview of scan IOU in search displays [User] owes [amount] currency 0 expense
What is the root cause of that problem?
In getLastMessageTextForReport https://github.com/Expensify/App/blob/280303ff3adfe4915c313d0ed25eb5c3923788b6/src/libs/OptionsListUtils/index.ts#L596
lastReportAction is undefined and so the entire logic ends up with displaying the result of
const lastVisibleMessage = getLastVisibleMessage(report?.reportID);
https://github.com/Expensify/App/blob/280303ff3adfe4915c313d0ed25eb5c3923788b6/src/libs/OptionsListUtils/index.ts#L615
Which basically just takes message from the IOU report action https://github.com/Expensify/App/blob/280303ff3adfe4915c313d0ed25eb5c3923788b6/src/libs/ReportActionsUtils.ts#L1068
And in case of scanned expenses, this message is stale as it seems that when the scanning is done, the reportAction message does not actually get updated.
What changes do you think we should make in order to solve the problem?
We can modify somewhere around this line https://github.com/Expensify/App/blob/280303ff3adfe4915c313d0ed25eb5c3923788b6/src/libs/OptionsListUtils/index.ts#L790
// For IOU/Expense reports with receipts, generate message from current transaction data instead of using stale cached message
// This ensures scanned receipts show the actual scanned amount, not the initial "0" amount
// We only do this for reports with receipts because manual expenses already have correct cached messages
if (!lastReportAction && !lastMessageTextFromReport && reportID && (reportUtilsIsMoneyRequestReport(report) || isExpenseReport(report))) {
const transactions = getReportTransactions(reportID);
const hasReceiptTransactions = transactions.some((transaction) => hasReceiptTransactionUtils(transaction));
if (hasReceiptTransactions) {
// Find the IOU action from all report actions for this report
const reportActionsForReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
const iouAction = reportActionsForReport ? Object.values(reportActionsForReport).find((action) => action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) : undefined;
const properSchemaForMoneyRequestMessage = getReportPreviewMessage(report, iouAction, true, false, null, true);
lastMessageTextFromReport = formatReportLastMessageText(Parser.htmlToText(properSchemaForMoneyRequestMessage));
}
}
In order to get the actual data from the transaction in this case.
I think it would also make sense, to have BE modify the message in the related action after the scanning is done.
What alternative solutions did you explore? (Optional)
@lanitochka17 the video does not match the steps provided. Only one receipt was scanned in the video and no additional manual expense was created.
Also, I am having trouble recreating — I wonder if it has anything to do with location services? Going to have this retested 👍
Job added to Upwork: https://www.upwork.com/jobs/~022003115447146418649
Triggered auto assignment to Contributor-plus team member for initial proposal review - @suneox (External)
This has been labelled "Needs Reproduction". Follow the steps here: https://stackoverflowteams.com/c/expensify/questions/16989
@lanitochka17 the video does not match the steps provided. Only one receipt was scanned in the video and no additional manual expense was created.
@CortneyOfstad I think step 5 and 6 shouldn't be there in OP as this issue is reproducible with only four steps. Maybe @lanitochka17 kept step 5 (create manual expense one) and 6 to compare the result for scanned and manual expense.
Also, I am having trouble recreating — I wonder if it has anything to do with location services? Going to have this retested 👍
There is nothing to do with location services, it is reproducible with first four steps.
This is a BE issue, where the message of the IOU action does not update after the scan is completed.
This is a BE issue, where the message of the IOU action does not update after the scan is completed.
According to me or what I've gone through, it isn't the Backend issue as I couldn't find any API which is running after scanning is completed so we can't expect backend to return the actual data.
May be there is no API designed after SmartScan completion.
I could be totally wrong but I'm posting a proposal after a lot of back and forth with the code.
Proposal
Please re-state the problem that we are trying to solve in this issue.
Incorrect message preview or subtitle is shown (of Scanned expense / IOU) in LHN and in Search.
What is the root cause of that problem?
The root cause of this issue lies inside src/libs/OptionsListUtils/index.ts file where we have getLastMessageTextForReport function which is responsible to generate LHN and Search list item subtitle or message previews.
In getLastMessageTextForReport function, when it runs to generate the subtitle of scanned expense then the following code runs and returns the lastVisibleMessage?.lastMessageText And as for scanned or manual expenses, it got lastReportAction undefined and lastOriginalReportAction defined:
// If the last action differs from last original action, it means there's a hidden action (like a whisper), then use getLastVisibleMessage to get the preview text
if (!lastMessageTextFromReport && !lastReportAction && !!lastOriginalReportAction) {
return lastVisibleMessage?.lastMessageText ?? '';
}
In case of manual expenses, we have the correct amount in lastVisibleMessage?.lastMessageText by getLastVisibleMessage i.e. <currency><amount> expense as the amount was known or manually typed when creating manual expenses so it is there in last report action (from which getLastVisibleMessage is getting last message)
BUT
in case of scanned expenses, the amount isn't know during scanning so last report action posses 0 amount and the lastVisibleMessage?.lastMessageText got staled (and after scanning, the amount of transaction is updated but the last report action amount isn't updated at all, which is stored in Onyx inside message property of report action, and we're getting <currency>0 expense by lastVisibleMessage?.lastMessageText, the staled one). That's the primary reason we are getting <currency>0 expense in LHN and Search.
Here is the image of incorrect LHN subtitle:
What changes do you think we should make in order to solve the problem?
In the following code snippet in getLastMessageTextForReport function:
// If the last action differs from last original action, it means there's a hidden action (like a whisper), then use getLastVisibleMessage to get the preview text
if (!lastMessageTextFromReport && !lastReportAction && !!lastOriginalReportAction) {
return lastVisibleMessage?.lastMessageText ?? '';
}
we are checking for hidden whispers but it will be truthy if it finds lastReportAction undefined and lastOriginalReportAction undefined (and of course the lastMessageTextFromReport is also undefined here) which is happening in case of scanned and manual expenses.
According to the comment in the code snippet, the condition is loosely guarded for catching whispers instead it should be strongly restricted to whispers only so that we won't get last message for both manual and scanned expenses.
Here is how we can guard it strongly by adding isWhisperAction so that it'll run only for whispers:
// If the last action differs from last original action, it means there's a hidden action (like a whisper), then use getLastVisibleMessage to get the preview text
if (!lastMessageTextFromReport && !lastReportAction && !!lastOriginalReportAction && isWhisperAction(lastOriginalReportAction)) {
return lastVisibleMessage?.lastMessageText ?? '';
}
If we do that we will get the following subtitle or preview for both manual and scanned expenses:
Which is ideal because we have the similar subtitle or preview in the Search for expense, here:
And we get the following subtitle preview in LHN if we go with the above solution:
INSTEAD OF THIS:
Which will be consistent across everywhere.
What alternative solutions did you explore? (Optional)
There is an alternate solution exist for this but I think we should consider the above solution as it is easier and efficient.
In src/libs/TransactionPreviewUtils.ts, we have getTransactionPreviewTextAndTranslationPaths function in which we are getting to know when the scanning of the expense is completed.
Based on the transaction status (with isTransactionScanning) and checking whether the scanning is completed or not we can add a condition inside this function which will merge or update the Onyx data for that report action in which it will update the message property which will ultimately update the staled message of last report action based on the current transaction data.
With this alternate solution, we will get the actual amount in the scanned expense message preview in LHN and Search.
Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.
@suneox I want to hear from your side about my proposed solution as I would prefer solution 1 because it is easier, efficient, won't hault the app's performance and it will make the message preview consistent.
@suneox Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
Thank for all proposals. I'll take a look at this one today
Currently, there is no proposal that meets the expected result The message preview of scanned IOU in search should display as: "[User] owes [amount] currency amount expense" as it works for manual expense
Additionally, the expected behavior is unclear when the amount appears twice. I think the expected result should be consistent: both before and after clearing the cache, the last message for an expense report should only display Expense. So @CortneyOfstad Could you please help confirm the expected behavior for displaying the last message of an expense report in search?
| Before | After |
|---|---|
@suhailpthaj We’re still waiting for confirmation on the expected behavior. Also, your proposal currently shows Expense, but it doesn’t fix the issue on the latest main.
@suhailpthaj We’re still waiting for confirmation on the expected behavior. Also, your proposal currently shows Expense, but it doesn’t fix the issue on the latest main.
@suneox I didn't get it, can you please tell me how my proposal doesn't fix the issue on the lastes main?
Did you mean that my proposal isn't achieving the expected behaviour that is currency amount expense instead it shows Expense instead?
@CortneyOfstad Also there is one thing which I thought, In search in expense chat, we are already showing x owes amount which shows the amount for that expense so showing the amount again in the subtitle (as currency amount expense) doesn't make a lot of sense.
@suneox I didn't get it, can you please tell me how my proposal doesn't fix the issue on the lastes main?
@suhailpthaj After applying the changes from your proposal, there’s no difference in either case (submitted or clear cache). It would be helpful if you could share a tested branch once we get confirmation on the expected behavior.
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸
expected result should be consistent: both before and after clearing the cache, the last message for an expense report should only display Expense.
@suneox I agree with this as well. We need to make sure that it is consistent and avoids extra language without reason.
@CortneyOfstad Also there is one thing which I thought, In search in expense chat, we are already showing x owes amount which shows the amount for that expense so showing the amount again in the subtitle (as currency amount expense) doesn't make a lot of sense.
This is a great point — if we're already showing it in one location inside the chat, we don't need to show it twice.
@CortneyOfstad So can we say that the expected behaviour or the last message in message preview in Search should be Expense instead of currency amount expense for manual and scanned expenses?
@suneox Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
@CortneyOfstad So can we say that the expected behaviour or the last message in message preview in Search should be
Expenseinstead ofcurrency amount expensefor manual and scanned expenses?
@suhailpthaj we should make it consistent with the current behavior after clear cache or re-signing in
@suhailpthaj we should make it consistent with the current behavior after clear cache or re-signing in
@suneox does it means we need to make the subtitle currency amount expense for scanned expenses before and after clearing the cache or re-signing?
as the current behavior is currency amount expense for manual expenses.
@suneox does it means we need to make the subtitle currency amount expense for scanned expenses before and after clearing the cache or re-signing? as the current behavior is currency amount expense for manual expenses.
@suhailpthaj Currently, after re-signing in, the subtitle shows Expense until the expense chat is opened.
https://github.com/user-attachments/assets/64526926-da7b-48a5-bc9a-69e44801925e
@suneox I agree with this as well. We need to make sure that it is consistent and avoids extra language without reason.
Based on the confirmation we show Expense when there is no chat or activity on the expense
@suneox Ah! Now I get it what you are asking and wondering from the video you shared, Let me explain.
When we clear the cache or re sign then the message preview still get currency 0 expense but when we clear the cache or re sign then all the report actions gets cleared and (According to me) the App is designed in such a way that it only loads all the report actions for the particular report when we open the report (or click on the report which you did in the video, basically you clicked on the item in Search box) or it calls OpenReport API because loading all report actions upfront might hault the performance so that's kind of lazy loading is implemented.
Here are the logs results which confirms what I'm saying:
After clearing the cache or re sign
Before opening the expense report
After opening the expense report
So when the report actions aren't loaded yet then the code doesn't go inside this statement and shows the expected behaviour:
// If the last action differs from last original action, it means there's a hidden action (like a whisper), then use getLastVisibleMessage to get the preview text
if (!lastMessageTextFromReport && !lastReportAction && !!lastOriginalReportAction) {
return lastVisibleMessage?.lastMessageText ?? '';
}
But when report actions are loaded (after opening the expense in our case) then we got this issue of wrong subtitle and I explained this well in the Root cause in my proposal.
These are the logs which I used:
if (reportID === "/* Paste the report ID for the scanned expense report */") {
console.log("Logs", {
lastMessageTextFromReport,
"lastVisibleMessage": lastVisibleMessage?.lastMessageText,
lastOriginalReportAction,
lastReportAction,
"report last message": report?.lastMessageText,
"Whisper action": isWhisperAction(lastOriginalReportAction),
});
}
You can get the reportID by logging the result in createOption function, pick that reportID which correspond the last message to currency 0 expense.
just in case if you want to test, paste these logs before the following code snippet in getLastMessageTextForReport function:
// If the last action differs from last original action, it means there's a hidden action (like a whisper), then use getLastVisibleMessage to get the preview text
if (!lastMessageTextFromReport && !lastReportAction && !!lastOriginalReportAction) {
return lastVisibleMessage?.lastMessageText ?? '';
}
@CortneyOfstad @suneox this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
@suneox
There is one more thing I wanna add.
From the root cause from my proposal, if we removed the following code snippet entirely then we will be getting Expense always as subtitle before and after clearing cache or open:
// If the last action differs from last original action, it means there's a hidden action (like a whisper), then use getLastVisibleMessage to get the preview text
if (!lastMessageTextFromReport && !lastReportAction && !!lastOriginalReportAction) {
return lastVisibleMessage?.lastMessageText ?? '';
}
But based on the comment, it seems like this was intentional and made for whispers so (as per the root case from Proposal) so we need to prevent the logic going inside this block, the easiest thing we can do is to guard this strongly by adding isWhisperAction or preventing this code if it is an expense report.
Based on https://github.com/Expensify/App/issues/78217#issuecomment-3697162202 we show Expense when there is no chat or activity on the expense
The solution I provided will always show the Expense (the expected behaviour) as preview when there is no chat or activity irrespective of the cache clearing, expense opening or re signing.
Let me know if you have any other questions or we are good to go with.
⚠️ @suhailpthaj Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.
📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸