Fix: imported contact name not shown in expense after creation - #94819
Conversation
Co-authored-by: Yauheni Horbach <ZhenjaHorbach@users.noreply.github.com>
Co-authored-by: Yauheni Horbach <ZhenjaHorbach@users.noreply.github.com>
|
@MelvinBot |
… cases Co-authored-by: Yauheni Horbach <ZhenjaHorbach@users.noreply.github.com>
|
@MelvinBot |
|
@ZhenjaHorbach 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] |
|
🤖 Done — this PR is now marked Ready for review (removed from draft state). Heads up before reviewers dig in: the Tests, QA Steps, and Screenshots/Videos sections are still placeholder TODOs, and the PR Author Checklist is unchecked. Those should be filled in for the review to proceed smoothly. |
|
@MonilBhavsar 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] |
|
🤖 I looked into the failing checks — there's no code fix to push here. The only two failing checks are:
Every code and test check is passing — Bun tests, ESLint, Prettier, TypeScript typecheck, all 8 These two To make these pass: the PR author posts the PR Author Checklist as a comment (or in the PR body), and the reviewer posts the Reviewer Checklist. |
|
@MelvinBot |
|
@ZhenjaHorbach mind filling the author checklist? |
|
@MelvinBot |
I will complete it today or tomorrow! |
| } | ||
|
|
||
| const shouldCreateOptimisticPersonalDetails = isNewChatReport && !(personalDetails?.[payerAccountID] ?? allPersonalDetails[payerAccountID]); | ||
| // For imported device contacts the name is carried on `text`/`firstName`/`lastName` rather than |
There was a problem hiding this comment.
| // For imported device contacts the name is carried on `text`/`firstName`/`lastName` rather than | |
| // For imported device contacts the name is carried on text/firstName/lastName rather than |
|
|
||
| const shouldCreateOptimisticPersonalDetails = isNewChatReport && !(personalDetails?.[payerAccountID] ?? allPersonalDetails[payerAccountID]); | ||
| // For imported device contacts the name is carried on `text`/`firstName`/`lastName` rather than | ||
| // `displayName`, so fall back through those before defaulting to the login/phone number. Otherwise |
There was a problem hiding this comment.
| // `displayName`, so fall back through those before defaulting to the login/phone number. Otherwise | |
| // displayName, so fall back through those before defaulting to the login/phone number. Otherwise |
| // the contact's name is lost and the optimistic record falls back to the phone number. | ||
| const optimisticPersonalDetailFirstName = participant.firstName ?? ''; | ||
| const optimisticPersonalDetailLastName = participant.lastName ?? ''; | ||
| // These fields can each be an empty string, so pick the first non-empty candidate rather than using `??`. |
There was a problem hiding this comment.
self explanatory or unnecessary, IMO
| // These fields can each be an empty string, so pick the first non-empty candidate rather than using `??`. |
|
🤖 I looked into the failing checks — there's nothing to fix in the code. The only two failing checks are:
Every real CI check is passing — Jest (all 8 jobs), Bun tests, Why the checklist checks fail: they're gated on the checkboxes in the PR body being ticked. The To clear them: |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari |
|
@MelvinBot |
Co-authored-by: Yauheni Horbach <ZhenjaHorbach@users.noreply.github.com>
|
@MonilBhavsar 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] |
|
MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR. Next steps: A human should review the recent commits and resolve the conflicting CI errors manually. |
|
🤖 Done,
Note: I couldn't run Prettier locally (its plugin deps aren't installed in this environment), but the changes are comment-only and don't affect formatting. Please give the CI checks a look once they run. |
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp2026-07-07.16.41.44.movAndroid: mWeb ChromeNA iOS: HybridApp2026-07-07.16.32.14.moviOS: mWeb SafariNA MacOS: Chrome / SafariNA |
|
@MelvinBot PR Author Checklist
Screenshots/VideosAndroid: NativeAndroid: mWeb ChromeiOS: NativeiOS: mWeb SafariMacOS: Chrome / SafariScreen.Recording.2026-07-07.at.15.34.33.mov |
PR Author Checklist
Screenshots/VideosAndroid: NativeAndroid: mWeb ChromeiOS: NativeiOS: mWeb SafariMacOS: Chrome / SafariScreen.Recording.2026-07-07.at.15.34.33.mov |
|
@MelvinBot PR Author Checklist
Screenshots/VideosAndroid: NativeAndroid: mWeb ChromeiOS: NativeiOS: mWeb SafariMacOS: Chrome / SafariScreen.Recording.2026-07-07.at.15.34.33.mov |
|
🤖 Done — replaced the author checklist in the PR description with the full checklist you provided. |
|
Is the video correct? |
|
For Melvin's author checklist, I used a random checklist |
|
This has the correct one, mind updating it please #94819 (comment) |
|
@MelvinBot PR Author Checklist
Screenshots/VideosAndroid: HybridApp2026-07-07.16.41.44.movAndroid: mWeb ChromeNA iOS: HybridApp2026-07-07.16.32.14.moviOS: mWeb SafariNA MacOS: Chrome / SafariNA |
|
|
|
@MelvinBot PR Author Checklist
Screenshots/VideosAndroid: HybridApp2026-07-07.16.41.44.movAndroid: mWeb ChromeNA iOS: HybridApp2026-07-07.16.32.14.moviOS: mWeb SafariNA MacOS: Chrome / SafariNA |
|
|
|
🚧 MonilBhavsar has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/MonilBhavsar in version: 9.4.34-0 🚀
|
HelpDot review: no help-site changes requiredI reviewed the changes in this PR against Expensify's help-site articles under Why: This is an internal bug fix. It corrects an optimistic-data problem where an imported device contact's name reverted to their phone number in the report header and expense "From" field after an expense was created ( Help-site articles describe user-facing flows and features at a conceptual level. The affected behavior (how a participant's display name is resolved from optimistic personal details) is not documented in any article, and the fix doesn't change any documented step. I searched Because no changes are required, I did not create a draft help-site PR, apply the
@ZhenjaHorbach, please confirm you agree that no help-site changes are required. If you believe an article should be updated, let me know which flow/article and I'll create the draft PR. |
|
PR was failed on Android with an original issue cc @MonilBhavsar @JmillsExpensify 1784060444789.9afa6309-31a1-41aa-ace4-544a5a17336e.mp4 |
|
🚀 Deployed to production by https://github.com/roryabraham in version: 9.4.34-14 🚀
|
|
🚀 Deployed to production by https://github.com/roryabraham in version: 9.4.34-14 🚀
Bundle Size Analysis (Sentry): |

Explanation of Change
When an expense is created against an imported device contact (one with no Expensify account yet), the contact's name was shown correctly during selection/confirmation but reverted to the phone number in the report header and expense detail "From" field after the expense was created.
This happened because of two independent break points, both in
Expensify/App:The contact's name was never persisted to Onyx. The optimistic personal detail built in
getMoneyRequestInformationread onlyparticipant.displayName(undefined for device contacts) before falling back to the phone login. For imported contacts the name actually lives onparticipant.text/participant.firstName/participant.lastName, which were ignored — so the phone number got persisted.Even a persisted name would have been ignored.
getDisplayNameForParticipantshort-circuits to the formatted login for any record flaggedisOptimisticPersonalDetail: true, so the post-creation report views always rendered the phone number.This PR fixes both:
getMoneyRequestInformationnow builds the optimistic personal detail from the first non-empty name candidate (displayName→text→firstName + lastName), and persistsfirstName/lastNamealongside a realdisplayName.getDisplayNameForParticipantnow prefers a real display name on an optimistic record before falling back to the formatted login. For invite/new-user stubs (whose optimisticdisplayNameis set equal to the login) the resolved value is unchanged, so the original "don't fall back to Hidden when searching for a new user" behavior is preserved.Fixed Issues
$ #94644
PROPOSAL: #94644 (comment)
Tests
Offline tests
Same as above.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: HybridApp
2026-07-07.16.41.44.mov
Android: mWeb Chrome
NA
iOS: HybridApp
2026-07-07.16.32.14.mov
iOS: mWeb Safari
NA
MacOS: Chrome / Safari
NA