Skip to content

Show Explain button on receipt scan failed system messages#93532

Open
Beamanator wants to merge 2 commits into
mainfrom
beaman/612790-app-smartscan-explain
Open

Show Explain button on receipt scan failed system messages#93532
Beamanator wants to merge 2 commits into
mainfrom
beaman/612790-app-smartscan-explain

Conversation

@Beamanator

@Beamanator Beamanator commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

When SmartScan fails to parse a receipt, Auth posts a RECEIPTSCANFAILED report action in the transaction thread. As of Auth PR 21123 (merged 2026-06-13), that action now carries a reasoning field on originalMessage — an LLM-friendly description of what was/wasn't extracted — and hasReasoning(action) is what activates the inline Explain affordance everywhere else in the App.

The gap before this PR: ReceiptScanFailedContent rendered the action with a plain ReportActionItemBasicMessage, which has no hasReasoning() awareness. So reasoning was being delivered but the inline Explain link never appeared. (The right-click / long-press context menu's Explain entry — which gates on hasReasoning(reportAction) directly in ContextMenuActions.tsx — was already working, but the much more discoverable inline affordance was not.)

This PR:

  1. In ReceiptScanFailedContent.tsx — when hasReasoning(action) is true, renders through ReportActionItemMessageWithExplain (passing action, childReport, and originalReport), matching the pattern used by MovedTransactionAction, ExportIntegration, ModifiedExpenseContent, UnreportedTransactionAction, and ApprovalFlowContent. Otherwise falls back to the existing ReportActionItemBasicMessage rendering.
  2. In ActionContentRouter.tsx — threads originalReport down to ReceiptScanFailedContent (required by the WithExplain wrapper's explain() call).
  3. In src/types/onyx/OriginalMessage.ts — adds reasoning?: string to OriginalMessageSmartScanFailed for type accuracy. (Behaviour unchanged: the runtime hasReasoning() check is 'reasoning' in originalMessage, so this is type hygiene, not a behavioral fix.)

The user-visible message string is unchanged — translate('violations.smartscanFailed', {canEdit, missingFields}) still drives the human-readable copy (Receipt scanning failed — missing <fields>. Enter details manually.) for both branches.

Parent feature: the Explain feature rollout.

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/612790
PROPOSAL:

Tests

These are reproducible against a local dev build where the merged Auth PR is deployed (i.e. the Auth service on staging/prod, or the local Auth checkout past commit d67a2a0b).

  1. Sign in as a user able to submit receipts (no scan-limit gate, validated email).
  2. From a 1:1 DM or workspace chat, start a new submit-expense flow and upload a receipt that SmartScan cannot parse. Reliable options:
    • Upload a screenshot of a non-receipt image (e.g. a chat screenshot).
    • Upload a blank or solid-colour image.
    • Upload a heavily blurred or partially cropped real receipt.
  3. Wait for SmartScan to finish (typically 30s–2 minutes). The transaction thread will receive a system message from Concierge.
  4. Open the transaction thread (tap the expense preview to drill in).
  5. Confirm the system message reads: Receipt scanning failed — missing <field(s)>. Enter details manually. — the exact field list reflects what couldn't be extracted (one of merchant, date, amount, or multiple).
  6. Core verification: confirm an inline Explain link is appended after that message (rendered as a muted, clickable link).
  7. Tap the Explain link.
  8. Confirm a thread opens off the system message and Concierge replies with a field-aware explanation referencing the specific missing fields — e.g. "The date couldn't be read from this receipt..." rather than the generic "Receipt scanning failed". This is the reasoning field round-tripping through the Explain flow.
  9. Regression check: long-press (mobile) or right-click (web) the system message and confirm the context menu still shows the Explain option (this path was already working pre-PR via ContextMenuActions.tsx and must not break).
  10. Backward-compat check: if your account has any historical RECEIPTSCANFAILED action from before the merged Auth PR (i.e. no reasoning field on originalMessage), confirm it renders the same as before — message text only, no inline Explain link.
  • Verify the inline Explain link is present on a freshly-failed scan.
  • Verify tapping Explain opens a Concierge thread with a field-aware reply.
  • Verify the context-menu Explain entry still works (regression).
  • Verify legacy scan-failed actions without reasoning render without the inline link (backward compat).
  • Verify no errors appear in the JS console.

Offline tests

Tapping Explain is a normal Concierge round-trip; offline behaviour mirrors any other Explain action (e.g. Moved transaction, Exported to integration):

  1. With network on, trigger a SmartScan failure and confirm the system message + inline Explain link render in the transaction thread.
  2. Disable network (airplane mode / browser devtools "Offline").
  3. Re-open the same transaction thread — confirm the system message and the inline Explain link still render from cached Onyx data.
  4. Tap Explain while offline — the action should be queued (no immediate Concierge response).
  5. Re-enable network — confirm Concierge's reply appears in the newly opened thread once requests flush.

QA Steps

QA can run against staging once the merged Auth PR has deployed.

  1. Sign in to staging on a test account that can submit receipts.
  2. From a chat, upload a non-receipt image (a screenshot, blank image, or low-quality receipt photo) via the receipt-attach flow.
  3. Wait for SmartScan to finish — the failure system message will appear in the transaction thread.
  4. Open the transaction thread.
  5. Verify the message reads Receipt scanning failed — missing <field(s)>. Enter details manually. and the exact missing-field list matches what was undetectable.
  6. Verify an inline Explain link is present immediately after the message text.
  7. Tap Explain.
  8. Verify a thread opens off the system message and Concierge responds with a field-aware explanation (the reply must reference the specific missing fields, not the generic copy).
  9. Long-press (mobile) or right-click (web) the system message — verify the context-menu Explain entry is also still present (regression check).
  10. Verify no errors appear in the JS console.
  • All steps above succeed on staging.
  • No console errors during the flow.

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline tests section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (backward-compat check for legacy actions without reasoning; offline tap-Explain behavior)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick) — N/A, no new callbacks
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method — no new copy added; existing violations.smartscanFailed translation reused
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message: — N/A, no new text added
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods — N/A
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue) — N/A, no new copy
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README. — N/A, no new files
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers — N/A, reused the existing hasReasoning + ReportActionItemMessageWithExplain pattern
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected) — the new originalReport prop on ReceiptScanFailedContent is required and supplied at the only call site in ActionContentRouter.tsx; no other consumers exist
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly — ReceiptScanFailedContent's prop type gained the required originalReport field; updated at its single call site
  • If any new file was added I verified that: — N/A, no new files
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that: — N/A, no new styles
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that: — N/A, no asset changes
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic. — N/A, system-message rendering only, no markdown-input path touched
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases) — N/A, modifies an action-specific renderer, not a generic component
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. — N/A, no Storybook stories for this component
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account. — N/A, system message rendering only
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles: — adds an inline link via the existing ReportActionItemMessageWithExplain wrapper; no new buttons, no padding/spacing changes, no new form input styles
    • I verified that all the inputs inside a form are aligned with each other. — N/A
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes. — N/A, reuses an existing affordance unchanged
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page. — N/A, no new page
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

The .reasoning field is now populated on RECEIPTSCANFAILED actions by
Auth #21123, but ReceiptScanFailedContent renders via the bare
ReportActionItemBasicMessage which does not check hasReasoning().
Switch to ReportActionItemMessageWithExplain when reasoning is present
so users see the inline Explain link and can thread on the message.
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
...nbox/report/actionContents/ActionContentRouter.tsx 91.02% <ø> (ø)
...report/actionContents/ReceiptScanFailedContent.tsx 88.23% <75.00%> (-11.77%) ⬇️
... and 9 files with indirect coverage changes

@Beamanator

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 2601cc786e

ℹ️ 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".

@Beamanator Beamanator marked this pull request as ready for review June 13, 2026 18:27
@Beamanator Beamanator requested review from a team as code owners June 13, 2026 18:27
@melvin-bot melvin-bot Bot requested review from brunovjk and heyjennahay and removed request for a team June 13, 2026 18:27
@melvin-bot

melvin-bot Bot commented Jun 13, 2026

Copy link
Copy Markdown

@brunovjk 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]

@melvin-bot melvin-bot Bot removed the request for review from a team June 13, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant