App icon indicating copy to clipboard operation
App copied to clipboard

[$250] Search - Message preview of scan IOU in search displays [User] owes [amount] currency 0 expense

Open lanitochka17 opened this issue 3 weeks ago • 11 comments

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:

  1. Open https://staging.new.expensify.com
  2. Navigate to 1:1 conversation which not create expense before
  3. Create a scanned expense
  4. Click on search icon and observe the message preview of this IOU
  5. Back to conversation and create a manual expense
  6. 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

View all open jobs on GitHub

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 OwnerCurrent Issue Owner: @suneox

lanitochka17 avatar Dec 20 '25 16:12 lanitochka17

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.

melvin-bot[bot] avatar Dec 20 '25 16:12 melvin-bot[bot]

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.

Image

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)

imadirtydog avatar Dec 21 '25 20:12 imadirtydog

@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 👍

CortneyOfstad avatar Dec 22 '25 14:12 CortneyOfstad

Job added to Upwork: https://www.upwork.com/jobs/~022003115447146418649

melvin-bot[bot] avatar Dec 22 '25 14:12 melvin-bot[bot]

Triggered auto assignment to Contributor-plus team member for initial proposal review - @suneox (External)

melvin-bot[bot] avatar Dec 22 '25 14:12 melvin-bot[bot]

This has been labelled "Needs Reproduction". Follow the steps here: https://stackoverflowteams.com/c/expensify/questions/16989

MelvinBot avatar Dec 22 '25 14:12 MelvinBot

@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.

suhailpthaj avatar Dec 22 '25 17:12 suhailpthaj

This is a BE issue, where the message of the IOU action does not update after the scan is completed.

dmkt9 avatar Dec 25 '25 02:12 dmkt9

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.

suhailpthaj avatar Dec 25 '25 17:12 suhailpthaj

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:

Image

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:

Image

Which is ideal because we have the similar subtitle or preview in the Search for expense, here:

Image

And we get the following subtitle preview in LHN if we go with the above solution:

Image

INSTEAD OF THIS:

Image

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.

suhailpthaj avatar Dec 25 '25 17:12 suhailpthaj

@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.

suhailpthaj avatar Dec 25 '25 17:12 suhailpthaj

@suneox Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

melvin-bot[bot] avatar Dec 26 '25 00:12 melvin-bot[bot]

Thank for all proposals. I'll take a look at this one today

suneox avatar Dec 26 '25 12:12 suneox

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
Image Image

suneox avatar Dec 28 '25 07:12 suneox

@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 avatar Dec 28 '25 07:12 suneox

@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.

suhailpthaj avatar Dec 28 '25 09:12 suhailpthaj

@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.

suneox avatar Dec 29 '25 15:12 suneox

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

melvin-bot[bot] avatar Dec 29 '25 16:12 melvin-bot[bot]

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 avatar Dec 29 '25 18:12 CortneyOfstad

@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?

suhailpthaj avatar Dec 29 '25 18:12 suhailpthaj

@suneox Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

melvin-bot[bot] avatar Jan 02 '26 00:01 melvin-bot[bot]

@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?

@suhailpthaj we should make it consistent with the current behavior after clear cache or re-signing in

suneox avatar Jan 02 '26 10:01 suneox

@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.

suhailpthaj avatar Jan 02 '26 11:01 suhailpthaj

@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 avatar Jan 03 '26 09:01 suneox

@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

Image

After opening the expense report

Image

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 ?? '';
    }

suhailpthaj avatar Jan 03 '26 11:01 suhailpthaj

@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!

melvin-bot[bot] avatar Jan 03 '26 21:01 melvin-bot[bot]

@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 avatar Jan 04 '26 15:01 suhailpthaj

⚠️ @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.

github-actions[bot] avatar Jan 04 '26 15:01 github-actions[bot]

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

melvin-bot[bot] avatar Jan 05 '26 16:01 melvin-bot[bot]