Skip to content

[Payment due @dukenv0307] Show field-specific SmartScan failure messages#89454

Merged
justinpersaud merged 32 commits into
mainfrom
claude-smartscanFieldSpecificMessages
May 19, 2026
Merged

[Payment due @dukenv0307] Show field-specific SmartScan failure messages#89454
justinpersaud merged 32 commits into
mainfrom
claude-smartscanFieldSpecificMessages

Conversation

@MelvinBot
Copy link
Copy Markdown
Contributor

@MelvinBot MelvinBot commented May 3, 2026

Explanation of Change

When a receipt SmartScan fails, the violation message now specifies which fields are missing (e.g., "Receipt scanning failed — missing date. Enter details manually.") instead of the generic "Receipt scanning failed. Enter details manually."

The backend (Auth PR: https://github.com/Expensify/Auth/pull/21383) now includes a missingFields array in the smartscanFailed violation data. This PR updates the frontend to read that data and display field-specific messages.

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/632549

Tests

I manually inserted failures to the db to get the message to showing different failure cases

image image image
  • Verify that no errors appear in the JS console

Offline tests

N/A — This change only affects how existing violation data is displayed, not how it's fetched or stored.

QA Steps

  1. Upload an image of something that is not a receipt so that it will trigger field violations such as merchant, date, or amount. I used this:
image
  1. Wait for the image to smartscan, and then when the violation appears on the expense, confirm it shows a violation message specifically for the violation fields. It'll have multiple like this:
  • Verify that no errors appear in the JS console

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 steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • 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)
    • 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
      • 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:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • 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)
    • 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.
    • 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
  • 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)
  • 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
  • If any new file was added I verified that:
    • 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:
    • 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:
    • 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.
  • 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)
  • 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.
  • 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.
  • 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:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • 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.
  • 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.

AI Tests

Could not run App tests locally due to Node.js version mismatch in the CI environment (requires 20.20.0, had 22.20.0). CI will validate.

When a receipt SmartScan fails, the violation message now specifies which
fields are missing (e.g., "Receipt scanning failed — missing date") instead
of the generic "Receipt scanning failed" message.

The violation data from the backend now includes a missingFields array
(e.g., ["date"], ["merchant", "amount"]) which the frontend uses to build
the field-specific message. Falls back to the generic message when
missingFields is absent for backward compatibility.

Updated all 10 language files with the new message format.

Co-authored-by: David Barrett <quinthar@users.noreply.github.com>
@MelvinBot MelvinBot requested a review from a team May 3, 2026 18:51
@codecov
Copy link
Copy Markdown

codecov Bot commented May 3, 2026

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 Δ
...nents/ReportActionItem/MoneyRequestReceiptView.tsx 68.83% <100.00%> (+0.14%) ⬆️
src/libs/DebugUtils.ts 68.37% <ø> (-0.43%) ⬇️
src/libs/Violations/ViolationsUtils.ts 77.23% <50.00%> (+0.05%) ⬆️
...box/report/actionContents/SimpleMessageContent.tsx 91.54% <88.00%> (-2.21%) ⬇️
... and 123 files with indirect coverage changes

The smartscanFailed function in fr.ts used Unicode curly quotes
(U+2018/U+2019) as JavaScript string delimiters, which are not
valid JS syntax. Replaced them with regular apostrophes (U+0027)
while preserving the French apostrophe (U+2019) in template
literal content like L'analyse.

Co-authored-by: David Barrett <quinthar@users.noreply.github.com>
@MelvinBot
Copy link
Copy Markdown
Contributor Author

Fixed the failing checks. The smartscanFailed function in fr.ts used Unicode curly quotes (U+2018/U+2019) as JavaScript string delimiters instead of regular apostrophes. This caused Prettier, typecheck, and generateTranslations to all fail with syntax errors. Replaced the curly quotes with regular apostrophes while preserving the French apostrophe in template literal content.

Co-authored-by: David Barrett <quinthar@users.noreply.github.com>
@MelvinBot
Copy link
Copy Markdown
Contributor Author

Fixed the failing typecheck: added missingFields: 'array' to the violation data type map in DebugUtils.ts — it was missing after the new missingFields property was added to TransactionViolationData.

@justinpersaud justinpersaud self-assigned this May 5, 2026
@OSBotify

This comment has been minimized.

The backend sends English field keys (merchant, date, amount) in the
violation data. Translate them to each language's word via a fieldNames
map so non-English locales no longer render English keys mid-sentence.
Replace the hardcoded English " and " between the last two missing-field
items with each language's equivalent: und, y, et, e, en, i, e.
Japanese and Chinese join enumerated items with the ideographic comma
(、) and don't insert an "and" before the last item. Replace the
length-based ', ' / ' and ' join with a uniform '、' separator. Also
drop the ASCII space before the field list in Chinese, which isn't
needed between Chinese characters.
Languages with verb conjugation use the wrong form when the count is
greater than one. Pick singular vs plural based on missingFields.length
in de (fehlt/fehlen), es (falta/faltan), it (mancante/mancanti), and nl
(ontbreekt/ontbreken). Switch the French phrasing from "X manquant" to
"il manque X", which avoids gender and number agreement entirely.
Receipt::buildScanFailedMessage in Auth uses ", and " before the last
item when listing 3+ missing fields ("merchant, date, and amount").
Match that style in en.ts so the violation message and the report
action that fires on scan failure read the same way.
@justinpersaud justinpersaud force-pushed the claude-smartscanFieldSpecificMessages branch from 2ecc112 to c238675 Compare May 5, 2026 21:15
@justinpersaud justinpersaud marked this pull request as ready for review May 6, 2026 17:25
@justinpersaud justinpersaud requested a review from a team as a code owner May 6, 2026 17:25
@melvin-bot melvin-bot Bot requested review from dukenv0307 and trjExpensify and removed request for a team May 6, 2026 17:25
@melvin-bot
Copy link
Copy Markdown

melvin-bot Bot commented May 6, 2026

@dukenv0307 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 May 6, 2026 17:25
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

🚧 @trjExpensify has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@trjExpensify trjExpensify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this PR be updating the Concierge message here?

Image

... because looking at the expense David flagged this on, it's highlighting a gap in the current solution.

  1. User uploaded a receipt, the SmartScan failed.
  2. User enters the details manually to clear the missing details violation.
  3. The SmartScanEverything receipt audit check flags the expense to the approver as the details were not read by SmartScan:
Image
  1. So if part of the point for this change is to allow the approver to see with ease what wasn't read by SmartScan, without the Concierge message copy update, they still won't gain that insight.

@justinpersaud
Copy link
Copy Markdown
Contributor

@trjExpensify The concierge message is a separate bug unrelated to this. It is being caused by regressions introduced by this PR #86896

@grgia and @MonilBhavsar were looking into that one.

@dukenv0307
Copy link
Copy Markdown
Contributor

@codex review

@melvin-bot
Copy link
Copy Markdown

melvin-bot Bot commented May 13, 2026

🎯 @dukenv0307, thanks for reviewing and testing this PR! 🎉

A payment issue will be created for your review once this PR is deployed to production.
E/E issue linked to the PR - https://www.github.com/Expensify/Expensify/issues/632549.

If payment is not needed (e.g., regression PR review fix etc), react with 👎 to this comment to prevent the payment issue from being created.

const actionReportID = action.reportID;
// Prefer parentReportActionID (specific IOU action when `report` is a transaction thread).
// Fall back to childReportID match, then to the only IOU action for one-transaction reports.
const getIouActionSelector = useCallback(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CLEAN-REACT-PATTERNS-0 (docs)

React Compiler is enabled and this file compiles successfully. The useCallback wrapping getIouActionSelector is redundant because the compiler automatically memoizes closures based on their captured variables.

Remove the useCallback wrapper and define getIouActionSelector as a plain function:

const getIouActionSelector = (reportActions: OnyxEntry<OnyxTypes.ReportActions>): OnyxTypes.ReportAction | undefined => {
    if (!isIouReport && parentReportActionID) {
        const candidate = reportActions?.[parentReportActionID];
        if (isActionOfType(candidate, CONST.REPORT.ACTIONS.TYPE.IOU)) {
            return candidate;
        }
    }
    const iouActions = Object.values(reportActions ?? {}).filter((a): a is OnyxTypes.ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> =>
        isActionOfType(a, CONST.REPORT.ACTIONS.TYPE.IOU),
    );
    if (actionReportID) {
        const match = iouActions.find((a) => a.childReportID === actionReportID);
        if (match) {
            return match;
        }
    }
    return iouActions.length === 1 ? iouActions.at(0) : undefined;
};

Reviewed at: 96e01d4 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinBot please assess

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Codex suggestion is valid. React Compiler is enabled in this repo (babel-plugin-react-compiler) and automatically memoizes closures based on captured variables. The useCallback wrapping getIouActionSelector is redundant — it should be replaced with a plain function assignment.

This aligns with the CLEAN-REACT-PATTERNS-0 coding standard enforced in this codebase.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinBot Let's fix it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — removed the useCallback wrapper and converted getIouActionSelector to a plain function. React Compiler handles memoization automatically. Pushed in 1c7073e.

Co-authored-by: dukenv0307 <dukenv0307@users.noreply.github.com>
Comment thread src/languages/es.ts
smartscanFailed: ({canEdit = true, missingFields = []}: {canEdit?: boolean; missingFields?: string[]}) => {
if (missingFields.length > 0) {
const fieldNames: Record<string, string> = {merchant: 'comerciante', date: 'fecha', amount: 'importe'};
const translated = missingFields.map((f) => fieldNames[f] ?? f);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some concerns about maintaining translations here with this default and extending this method in the future sending them from Auth

Comment thread src/pages/inbox/report/actionContents/SimpleMessageContent.tsx
Copy link
Copy Markdown
Contributor

@grgia grgia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB, missing unit tests, one comment that could be a concern

@github-actions
Copy link
Copy Markdown
Contributor

🚧 @grgia has triggered a test Expensify/App build. You can view the workflow run here.

@grgia
Copy link
Copy Markdown
Contributor

grgia commented May 18, 2026

@dukenv0307 @justinpersaud are we good to merge this one?

@justinpersaud
Copy link
Copy Markdown
Contributor

I think so, @dukenv0307 do you agree?

I had a backend PR to simplify this a bit more but I can always just do that as a follow up

@dukenv0307
Copy link
Copy Markdown
Contributor

Yes, we're good to merge

@grgia
Copy link
Copy Markdown
Contributor

grgia commented May 19, 2026

@MelvinBot we changed the BE to include missingFields

Please assess this plan:

Verified: payload lands on originalMessage

In Auth/auth/lib/ReportAction.cpp:6401-6418, every action returned
to clients is shaped as:

JSON::Value originalMessage = ... actionJSON["message"];
// line 6401
reportAction["message"] = ReportAction::buildMessage(...);
// line 6417 — segment array
reportAction["originalMessage"] = move(originalMessage);
// line 6418

actionJSON["message"] is the same JSON blob
UpdateReceiptState.cpp:63-91 writes (html, actionableForAccountIDs,
and now missingFields). So missingFields lands on
action.originalMessage.missingFields for RECEIPTSCANFAILED.

The action.message[0] segment is a built text/html chunk via
buildMessage — the missingFields array does not live there.

Two additional surfaces that should be updated

Reviewing usages of RECEIPT_SCAN_FAILED showed the App PR only
updates the inbox chat view. Two other surfaces call
translate('violations.smartscanFailed', {canEdit}) and still produce
the generic message:

  • src/libs/ReportNameUtils.ts:471-478 — used for the report name /
    parent thread title.
  • src/libs/OptionsListUtils/index.ts:811-814 — used for the LHN
    last-message preview.

Both already have the RECEIPTSCANFAILED action in hand
(parentReportAction / lastReportAction), so adding missingFields is
trivial — just pull from
getOriginalMessage(parentReportAction)?.missingFields ?? [].

Refined plan

  1. Type the action's payload —
    src/types/onyx/OriginalMessage.ts:1568

Replace [CONST.REPORT.ACTIONS.TYPE.RECEIPT_SCAN_FAILED]: never; with
a real type (and export it):

type OriginalMessageReceiptScanFailed = {
html?: string;
actionableForAccountIDs?: number[];
missingFields?: Array<'merchant' | 'date' | 'amount'>;
lastModified?: string;
};
[CONST.REPORT.ACTIONS.TYPE.RECEIPT_SCAN_FAILED]:
OriginalMessageReceiptScanFailed;

  1. Simplify ReceiptScanFailedContent —
    src/pages/inbox/report/actionContents/SimpleMessageContent.tsx:57-91

Drop the whole IOU-selector + useOnyx(TRANSACTION_VIOLATIONS_…)
chain. The IOU lookup for canEdit already has a simpler form in
ReportNameUtils.ts:473-476 — copy that pattern. Result is ~10 lines:

function ReceiptScanFailedContent({action, report}: {...}) {
const {translate} = useLocalize();
const missingFields = getOriginalMessage(action)?.missingFields
?? [];
// RECEIPT_SCAN_FAILED is submitted by Concierge; use the IOU
action to determine edit permission.
const [parentReport] = useOnyx(${ONYXKEYS.COLLECTION.REPORT}${g etNonEmptyStringOnyxID(report?.parentReportID)});
let iouAction = getReportAction(report?.parentReportID,
report?.parentReportActionID);
if (!isActionOfType(iouAction, CONST.REPORT.ACTIONS.TYPE.IOU)) {
iouAction = getReportAction(parentReport?.parentReportID,
parentReport?.parentReportActionID);
}
return <ReportActionItemBasicMessage
message={translate('violations.smartscanFailed', {canEdit:
wasActionTakenByCurrentUser(iouAction), missingFields})} />;
}

  1. Update the two other surfaces to pass missingFields
  • ReportNameUtils.ts:477 → translate('violations.smartscanFailed',
    {canEdit: wasActionTakenByCurrentUser(iouAction), missingFields:
    getOriginalMessage(parentReportAction)?.missingFields ?? []})
  • OptionsListUtils/index.ts:814 → same, passing lastReportAction to
    getOriginalMessage.
  1. Keep these as-is
  • src/languages/*.ts — translator already supports missingFields.
  • src/libs/Violations/ViolationsUtils.ts — violation surface (e.g.
    MoneyRequestReceiptView) still reads missingFields from
    violation.data; this path stays.
  • src/types/onyx/TransactionViolation.ts — missingFields?: string[]
    on TransactionViolationData stays.
  • src/libs/DebugUtils.ts — the validator entry stays.
  1. Split or drop the unrelated MoneyRequestReceiptView.tsx change

The localSource ?? source change isn't related to smartscan
messaging. Either move it to its own PR or confirm it was
intentional on this branch before keeping it.

@MelvinBot
Copy link
Copy Markdown
Contributor Author

Plan Assessment

The plan is mostly sound with a few concerns worth addressing before implementation.

What's correct

  • Step 1 (Type originalMessage): ✅ RECEIPT_SCAN_FAILED is currently never and needs a real type to read missingFields from it.
  • Step 3 (Update other surfaces): ✅ Confirmed — both ReportNameUtils.ts:475 and OptionsListUtils/index.ts:814 currently pass {canEdit} without missingFields — these are real gaps that produce the generic message.
  • Step 4 (Keep as-is): ✅ Translations already support missingFields, ViolationsUtils already passes it, types are fine.
  • Step 5 (MoneyRequestReceiptView): ✅ Already reverted per dukenv0307's review comment.

Concerns

Step 2 — Reactivity of IOU lookup in the component:

The plan proposes replacing the current reactive useOnyx selector chain with imperative getReportAction() calls (copied from ReportNameUtils.ts). That pattern works in ReportNameUtils because it runs outside React rendering, but inside a React component, getReportAction is not reactive — if the IOU action loads after the initial render (e.g., paginated actions still fetching), the component won't re-render with the correct canEdit value.

The current implementation's useOnyx selector (lines 66–84) is more verbose but stays reactive. I'd recommend keeping the useOnyx-based IOU lookup and only replacing the violation-fetching part with the originalMessage read.

Step 2 — Scope of simplification:

The plan drops the useOnyx(TRANSACTION_VIOLATIONS_…) chain entirely — that part is correct since missingFields would come from originalMessage instead. But removing the IOU useOnyx selector and replacing it with getReportAction is a separate change that trades reactivity for brevity. These should be evaluated independently.

Suggested revision to Step 2

Keep the existing useOnyx selector for the IOU action lookup (lines 66–84). Only replace the violation data source:

- const transactionID = getLinkedTransactionID(iouAction);
- const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getNonEmptyStringOnyxID(transactionID)}`);
- const smartscanFailedViolation = transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.SMARTSCAN_FAILED);
- const missingFields = smartscanFailedViolation?.data?.missingFields ?? [];
+ const missingFields = getOriginalMessage(action)?.missingFields ?? [];

This removes the 3-hop chain (IOU → transactionID → violations → missingFields) while keeping the IOU lookup reactive.

Summary

Step Verdict
1. Type originalMessage ✅ Do it
2. Simplify component ⚠️ Replace violation source only; keep reactive IOU lookup
3. Update ReportNameUtils + OptionsListUtils ✅ Do it
4. Keep translations/violations/types ✅ Correct
5. MoneyRequestReceiptView ✅ Already reverted

@justinpersaud
Copy link
Copy Markdown
Contributor

@grgia you wanting to handle that here or separately?

@grgia
Copy link
Copy Markdown
Contributor

grgia commented May 19, 2026

@justinpersaud fine to merge this and follow up or not. It would be better to have a contributor take that change moving forward as I'm focused on the merge HR sprint.

I've already assessed the changes needed above

@justinpersaud
Copy link
Copy Markdown
Contributor

ok lets do it, @dukenv0307 are you interested in the follow up? #89454 (comment)

@justinpersaud justinpersaud merged commit db327d0 into main May 19, 2026
75 checks passed
@justinpersaud justinpersaud deleted the claude-smartscanFieldSpecificMessages branch May 19, 2026 12:36
@github-actions
Copy link
Copy Markdown
Contributor

🚧 @justinpersaud has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify
Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/justinpersaud in version: 9.3.78-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@MelvinBot
Copy link
Copy Markdown
Contributor Author

Yes, help site changes are needed. The New Expensify Troubleshoot SmartScan Issues article references the old generic message "Receipt scanning failed. Enter details manually." in two places.

I've created a draft PR with the updates: #91129

Changes made:

  • Updated the failure message description to reflect the new field-specific format (e.g., "Receipt scanning failed — missing date. Enter details manually.")
  • Updated the step describing the red dot indicator to mention field-specific messages
  • Added keywords (missing fields, missing date, missing merchant, missing amount) for search discoverability

Note: The Expensify Classic version of the same article was not updated since this PR only changes New Expensify frontend code. If the classic app also picks up the backend missingFields data, that article would need a separate update.

@OSBotify
Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/roryabraham in version: 9.3.78-1 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@melvin-bot
Copy link
Copy Markdown

melvin-bot Bot commented May 21, 2026

🤖 Payment issue created: #91352

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.

8 participants