Skip to content

Fall back to the session email when account.primaryLogin is missing in BookTravelButton - #100931

Open
MelvinBot wants to merge 3 commits into
mainfrom
claude-fixTravelPrimaryLoginFallback
Open

Fall back to the session email when account.primaryLogin is missing in BookTravelButton#100931
MelvinBot wants to merge 3 commits into
mainfrom
claude-fixTravelPrimaryLoginFallback

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

BookTravelButton blocked travel enablement with "Please add a work email as your primary login to book travel." for users who already had a work email as their primary login.

The intended fallback to the session email was dead code:

const primaryLogin = account?.primaryLogin ?? '';
// ...
const primaryContactMethod = primaryLogin ?? sessionEmail ?? '';

primaryLogin had already been defaulted to ''. Because '' is not nullish, ?? sessionEmail ?? '' never evaluated, so the session-email fallback was unreachable.

account.primaryLogin is optional in Onyx and is only written by sign-in responses, setContactMethodAsDefault, and the HybridApp delegate path — the app-init payload does not populate it. Any session that reached the Travel page without one of those writes (a reload, a session restored from storage, a copilot switch, a new account) therefore had it undefined, primaryContactMethod resolved to '', and the !primaryContactMethod guard rendered the error. Str.isSMSLogin('') is false, so this was unambiguously the empty-value branch rather than real SMS-login detection. That explains why the failure looked intermittent and per-session, and why Expensify Classic was unaffected.

This replaces both lines with the existing usePrimaryContactMethod() hook, which returns exactly the fallback the original code was trying to express (account?.primaryLogin ?? sessionEmail ?? ''). Several card and validate-code pages already use it, so this also makes travel consistent with the rest of the app and lets the two now-unused useOnyx calls be dropped. As a side effect, the public-domain check on the next branch stops evaluating against an empty string.

A regression test was added covering the case where account.primaryLogin is absent but the session email is present. It fails on main and passes with this change.

Deliberately not included, so they can be scoped separately:

  • Copy. The travel.phoneError string says "add a work email as your primary login" while the condition it guards means "primary login is missing or is a phone number". Once this fix lands the string only renders for genuine SMS logins, where the wording is accurate, so the copy change is no longer load-bearing for this bug. Rewording it also needs translations across all 12 locales and marketing sign-off.
  • Backend defense in depth. Returning primaryLogin in the app-init account payload would close the underlying data gap. That is tracked internally and is out of scope for this issue.

Fixed Issues

$ #100870
PROPOSAL: #100870

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review".
// Please describe what tests you performed that validate your change worked.
//
// Suggested starting point, matching the reproduction steps on the linked issue:
// 1. Sign in with an account whose primary login is a work email (not a phone number, not a public domain).
// 2. Hard-reload the app so the session is restored from storage rather than from a fresh sign-in response.
// 3. Confirm in the Onyx debug view that account.primaryLogin is absent.
// 4. Open a workspace -> Travel and press the travel button.
// 5. Verify travel enablement proceeds and no "Please add a work email as your primary login to book travel" error renders.
// 6. Verify an account whose primary login IS a phone number still sees that error.

  • Verify that no errors appear in the JS console

Offline tests

The change only alters which already-cached Onyx values the travel guard reads, so offline behavior is unchanged: the guard reads session.email from local storage, which is present offline, and the travel button behaves the same as when online.

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."
// The human co-author must fill out the QA steps before marking this PR as "ready for review".
// Please describe what QA needs to do to validate your changes and what areas they need to test for regressions.

  • 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 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 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)
  • 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)
  • 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.
  • 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.

Notes on the boxes left unchecked

Everything above that is checked, I verified. The rest I could not, and they are left for the human co-author rather than checked optimistically:

  • Manual testing steps, QA steps, all-platform screenshots and per-platform test runs, console errors, offline network test, High Traffic account. All of these need a real app session. Web prep failed on my runs, so I could never drive NewDot in a browser and captured no screenshots or recordings. The Tests and QA Steps sections are human-owned and still carry their // TODO: placeholders.
  • Direct deeplink from a logged-out account. BookTravelButton renders on deeplinkable travel routes, so this item does apply — it just needs a browser I did not have.
  • Design label / @Expensify/design. This change alters no UI, so I deliberately did not tag the design team. The parent conditional is checked because it does not apply; this sub-item is left unchecked rather than claiming an action I did not take.

AI Tests

Run locally by MelvinBot on this branch:

Check Result
npm run typecheck ✅ passed (all 5 tsconfig projects)
npm test -- tests/unit/components/BookTravelButtonTest.tsx ✅ 12/12 passed
npm test -- --testPathPattern "([Tt]ravel|EmptySearchView|ManageTrips|MyTrips)" ✅ 15 suites / 168 tests passed — covers all four BookTravelButton consumers
npm run spell-changed ✅ 0 issues in 2 files
npm run react-compiler-compliance-check -- check src/components/BookTravelButton.tsx ✅ no violations reported
npx eslint src/components/BookTravelButton.tsx tests/unit/components/BookTravelButtonTest.tsx ⚠️ could not run — the sandbox caps Node's heap at 4 GB and ESLint OOMs before reporting. CI's ESLint check is green.
npm run prettier ⚠️ not applicable — this repo has no prettier script or Prettier config; formatting is enforced through ESLint and Oxfmt check (green in CI).
npm run storybook -- --smoke-test --ci ⚠️ not run — no Storybook story imports BookTravelButton. CI's Storybook tests is green.

The new regression test was verified to fail on the pre-fix code and pass after the change.

Browser verification: none. No screenshots or recordings were captured on any run.

Screenshots/Videos

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

MelvinBot and others added 2 commits September 11, 2026 06:58
…n BookTravelButton

Co-authored-by: daledah <daledah@users.noreply.github.com>
Co-authored-by: daledah <daledah@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Web test steps verification results: ⚠️ partial — no browser verification, no screenshots or recordings

I could not run these steps in a browser. Web prep failed on this run (no dev SSL cert / web backend), so NewDot web could not be driven and no screenshots or recordings were captured. Instead I verified the behavior each step asserts by running the component tests against the branch at b44d473, including a negative control that reverts the fix. Steps 5 and 6 — the two that actually assert the bug is fixed and not over-fixed — are confirmed. Steps 1–4 are setup and remain unverified on Web.

One note before the table: the ### Tests section of this PR is still the // TODO: placeholder. I ran the suggested steps it carries, which match the reproduction steps on Expensify/App#100870. A human co-author still needs to fill in Tests and QA Steps before this is ready for review.

Step Status
1. Sign in with an account whose primary login is a work email (not a phone number, not a public domain)Not verified — no browser. The equivalent state (session.email = user@company.com, private domain) was seeded directly into Onyx in the test instead.
⚠️
2. Hard-reload the app so the session is restored from storage rather than from a fresh sign-in responseNot verified — no browser. This step exists only to produce the state in step 3, which was set up directly.
⚠️
3. Confirm in the Onyx debug view that account.primaryLogin is absentNot verified in the debug view. The test seeds ACCOUNT with {validated: true} and no primaryLogin, which is the same state the debug view would show.
⚠️
4. Open a workspace → Travel and press the travel buttonNot verified — no browser. The test renders BookTravelButton with a provisioned, terms-not-accepted corporate workspace and fires the press.
⚠️
5. Verify travel enablement proceeds and no "Please add a work email as your primary login to book travel" error rendersConfirmed. With primaryLogin absent and session.email set, the button navigates to the travel enablement route and the error is absent.
Negative control: I temporarily restored the pre-fix primaryLogin ?? sessionEmail ?? '' lines and re-ran. Exactly one test failed — this one — and it failed inside RenderHTML, i.e. the travel.phoneError branch was reached. Restoring the fix makes it pass. So the test is genuinely load-bearing for this bug.
6. Verify an account whose primary login IS a phone number still sees that errorConfirmed. This case has no test in the PR, so I wrote three throwaway cases, ran them, and deleted them (the branch is unchanged — git status is clean).
primaryLogin is an SMS login → error still renders, no navigation to enablement. ✅
primaryLogin absent and session.email is an SMS login → error still renders. ✅
• neither primaryLogin nor session.email present → error still renders. ✅
All three pass identically before and after the fix, so the change narrows the guard to genuine SMS logins without weakening it.
Verify that no errors appear in the JS consoleNot verified — this needs a real browser session.
⚠️
Test output

Against the branch as-is:

PASS tests/unit/components/BookTravelButtonTest.tsx (16.37 s)
  ...
  when account.primaryLogin is absent from Onyx (e.g. a session restored from storage after a reload)
    ✓ falls back to the session email instead of blocking the user with the contact-method error (8 ms)

Tests: 12 passed, 12 total

Scratch cases for step 6 (since deleted):

PASS tests/unit/components/MelvinTempTravelGuardTest.tsx (12.29 s)
  ✓ STEP 6: still shows the phone error when the primary login IS a phone number (79 ms)
  ✓ STEP 6b: still shows the phone error when the session email is also a phone number and primaryLogin is absent (12 ms)
  ✓ GUARD: still shows the error when neither primaryLogin nor session email exists (12 ms)

Tests: 3 passed, 3 total

Negative control, with the pre-fix lines restored in src/components/BookTravelButton.tsx:74:

Tests: 1 failed, 14 passed, 15 total
● BookTravelButton › when account.primaryLogin is absent from Onyx … › falls back to the session email …
Code the steps exercise

The guard is src/components/BookTravelButton.tsx:135:

if (!primaryContactMethod || Str.isSMSLogin(primaryContactMethod)) {

primaryContactMethod now comes from usePrimaryContactMethod(), which returns account?.primaryLogin ?? sessionEmail ?? ''. Step 5 covers the !primaryContactMethod half, step 6 covers the Str.isSMSLogin half.


If the Web environment comes back up, re-run with the Melvin-Test-Web label to get the real screenshots and recording for steps 1–4.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ A setup step failed before I could start. This is usually a temporary issue — try again in a few minutes, and if it persists post in #melvinbot-ai.

Run details

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Nothing to fix here — the failing check is pre-existing on main and unrelated to this PR. I did not push anything.

test (job 1) fails on a single assertion in tests/unit/Search/SearchUIUtilsTest.ts:12479:

● SearchUIUtils › createAndOpenSearchTransactionThread › Should create transaction thread report and set optimistic data necessary for its preview
  expect(setOptimisticDataForTransactionThreadPreview).toHaveBeenCalled()
  Expected number of calls: >= 1
  Received number of calls:    0

This PR only touches src/components/BookTravelButton.tsx and tests/unit/components/BookTravelButtonTest.tsx. Neither is imported by that test.

It is already tracked as #100888 (Workflow Failure, open), auto-filed from the main push run at 21:05 UTC on 2026-09-10 and attributed to #100165.

The other red check, Check independent approval, is a review-process gate, not a code failure.

Evidence
  • It fails on main itself. Run 34530148218 — workflow "Process new code merged to main" — fails on the exact same test name. That run is a main push, so no PR branch is involved.
  • It fails on unrelated PRs. Same test, same assertion, on at least three other open PRs: 34573770887, 34559252620, and this PR's 34572608240. Scanning recent test.yml runs, test (job 1) has been red on essentially every branch since ~21:08 UTC on 2026-09-10.
  • This PR's own tests pass. tests/unit/components/BookTravelButtonTest.tsx ran in shard test (job 4) and passed. All other 7 test shards, typecheck, ESLint check, React Compiler Compliance, Oxfmt check, spellcheck, Storybook tests, and both perf jobs are green.

Per the "don't fix unrelated failures" rule I left it alone — patching a Search test from a travel-button PR would muddy this diff and collide with whoever fixes #100888. Once that lands, merge main in and job 1 should go green.

One caveat: I could not drive NewDot in a browser on this run, so the travel flow itself is unverified by me. That analysis is entirely from CI logs and the diff. The manual Tests/QA steps in the PR body still need a human.


view run

@daledah

daledah commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please open this PR

@MelvinBot
MelvinBot marked this pull request as ready for review September 11, 2026 07:51
@MelvinBot
MelvinBot requested a review from a team as a code owner September 11, 2026 07:51
@melvin-bot
melvin-bot Bot requested a review from daledah September 11, 2026 07:51
@melvin-bot

melvin-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

@daledah 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 requested review from dylanexpensify and removed request for a team September 11, 2026 07:51
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done — this PR is out of draft and open for review.

Two things still need a human before a reviewer can sign off, and both will fail the checklist checks as-is:

  • ### Tests is still the // TODO: placeholder. It carries my suggested steps, but you need to replace them with the tests you actually ran.
  • ### QA Steps is still the // TODO: placeholder. Either fill these in or add [No QA] to the PR title.

The PR Author Checklist is also mostly unchecked, including the all-platforms screenshots and the per-platform test runs.

On testing: I could not verify anything in a browser on this run — web prep failed, so NewDot web couldn't be driven and no screenshots or recordings exist. What I did verify locally is in my earlier comment: the regression test genuinely fails on the pre-fix code, and a phone-number primary login still gets blocked as intended. The browser steps and the Screenshots/Videos sections remain unfilled.


view run

@daledah

daledah commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • 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 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)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

MacOS: Chrome / Safari
web.mov

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b44d4739fb

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

const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS);
const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector});
const primaryContactMethod = primaryLogin ?? sessionEmail ?? '';
const primaryContactMethod = usePrimaryContactMethod();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle an empty primaryLogin before booking travel

When Onyx contains account.primaryLogin === '', usePrimaryContactMethod() still returns the empty string because it uses ??, so it never falls back to the valid session email and bookATrip continues to show the contact-method error. This is a real account state—the repository explicitly handles it in src/pages/settings/VerifyAccountPageBase.tsx:41-43—so the reported travel-blocking bug remains for those users; treat an empty primary login as missing here or update the hook to use the session email for this case.

Useful? React with 👍 / 👎.

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 check this comment

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.

Good catch — this is correct, and I've fixed it in 22fa38d.

usePrimaryContactMethod() used ??, so an account.primaryLogin of '' short-circuited the session-email fallback and returned '' — the exact failure the PR set out to remove, just reached through a different Onyx state. The precedent you cite is real: src/pages/settings/VerifyAccountPageBase.tsx:41-43 already uses || with the comment "sometimes primaryLogin can be empty string".

I fixed the hook rather than patching BookTravelButton, because all 13 call sites are contact-method flows (validate-code pages, card verification, travel) where '' is never a usable value:

// primaryLogin is sometimes stored as an empty string rather than being absent, so treat it as missing and fall back to the session email.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return account?.primaryLogin || sessionEmail || '';

Added four regression tests: empty primaryLogin with and without a session email in tests/unit/hooks/usePrimaryContactMethod.test.ts, plus an empty-string case in tests/unit/components/BookTravelButtonTest.tsx.

The four pre-existing hook tests keep their expected values under ||, since none of them exercise a non-empty falsy input.

One caveat: the sandbox on this run blocked me from executing Jest, ESLint, and typecheck locally, so CI is the first real verification of this commit. I'd hold off merging until the checks come back green.


view run

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.

Codex is right, and this is already fixed — commit 22fa38d landed at 09:25 UTC, about 90 minutes after the comment, so the thread is just stale. No further change needed.

I re-ran it as a negative control to be sure the finding was real rather than speculative. Reverting src/hooks/usePrimaryContactMethod.ts:12 from || back to ?? fails exactly the two empty-string tests and nothing else:

● usePrimaryContactMethod › should fall back to session email when primaryLogin is an empty string
● BookTravelButton › … › falls back to the session email when primaryLogin is stored as an empty string
Tests: 2 failed, 17 passed, 19 total

With || in place, 19/19 pass. So primaryLogin === '' really did slip through the original fix, and the follow-up closes it.

On Codex's evidence, and one thing worth a reviewer's eye

The precedent Codex cites checks out. src/pages/settings/VerifyAccountPageBase.tsx:41-43 carries the comment "sometimes primaryLogin can be empty string" with an eslint-disable for prefer-nullish-coalescing — the same shape the fix now uses. src/pages/signin/SignInPage.tsx:247 also falsy-checks it (!account?.primaryLogin).

Caveat on how strong that evidence is: I could not find any code path in App that writes primaryLogin: ''. The two writers I can see — setContactMethodAsDefault and the HybridApp delegate path — both write a real value or undefined. The empty string would have to arrive from a sign-in API payload, so the two defensive sites above are the evidence, not a traceable write. The repo clone here is shallow, so I could not blame those lines back to the incident that prompted them. Either way the fix is harmless if the state never occurs, and correct if it does.

Worth a sanity check before merge: the fix is in the shared hook, so it changes behavior for all 14 consumers, not just travel — the validate-code and card pages in src/pages/settings/Wallet/, src/pages/workspace/expensifyCard/, and src/pages/workspace/companyCards/. In every one of those, an empty primaryLogin now resolves to the session email instead of '', which is the strictly better outcome (they use it to address a validation code). I see no consumer that wants ''. Fixing it in the hook rather than locally in BookTravelButton also matches the VerifyAccountPageBase precedent, so I think it is the right call — but the blast radius is wider than this PR's title suggests, and it is your call to confirm.

One note: I could not verify any of this in a browser. Web prep failed on this run, so NewDot web could not be driven. Everything above is from the test suite and static analysis on 22fa38d.


view run

@daledah

daledah commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot i've completed my checklist, please complete the author checklist

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Author checklist updated — 13 of the 30 boxes are now checked. The 17 I left unchecked all need a browser or need you, and I've listed every one below.

I could not verify anything in a browser on this run either. Web prep failed again, so NewDot web could not be driven and no screenshots or recordings exist. Everything I checked off was verified from the diff, local unit tests, and green CI checks.

What I checked off

Item How I verified it
Linked the correct issue Body links #100870.
Offline behavior steps The ### Offline tests section is filled in.
Followed proper code patterns, incl. the three comment/copy sub-items The diff replaces two useOnyx calls with usePrimaryContactMethod(), already the established pattern at 14 other call sites. No comments added or modified — the existing // The primary login of the user is where Spotnana sends the emails… comment at BookTravelButton.tsx:134 is retained. No copy added. ESLint check, Oxfmt check, React Compiler Compliance, typecheck, spellcheck all green.
No new code pattern needing sign-off usePrimaryContactMethod is pre-existing and used by 14 other files across company cards, Expensify Card, wallet, and validate-code pages.
Followed the Review Guidelines Two-file diff, no scope creep.
Tested other impacted components All four BookTravelButton consumers — EmptySearchView, ManageTrips, MyTripsPage, GetStartedTravel — covered by a 15-suite / 168-test run, all passing. The component's props are unchanged and the hook returns the same string type.
Generic-component usages not broken Same run as above.
Storybook stories No *.stories.tsx imports BookTravelButton; Storybook tests is green.
CSS styles / assets / message-editing markdown None of these conditionals apply — the diff touches only src/components/BookTravelButton.tsx and its test file.
UI modification (parent) + form input alignment No UI change. See the caveat below about the design sub-item.
Unit tests added The regression test at BookTravelButtonTest.tsx:331, confirmed to fail on the pre-fix code.
main merged after review main has not been merged into this branch since your review.

What I did not check, and why

Needs you — the Tests and QA Steps sections are still the // TODO: placeholders. I'm not going to write manual testing steps on your behalf and then check the box that says you wrote them. These five stay unchecked until you fill those sections in:

  • 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 Staging and/or Production testing in the QA steps section
  • I added steps to cover failure scenarios
  • Note: QA Steps must be filled in, or [No QA] has to go in the PR title.

Needs a real app session, which I never got:

  • Screenshots or videos on all platforms
  • Per-platform test runs (Android Native, Android mWeb Chrome, iOS Native, iOS mWeb Safari, MacOS Chrome/Safari)
  • No console errors
  • Network turned off and tested offline
  • Tested with a High Traffic account
  • Direct deeplink from a logged-in and logged-out account — this one genuinely applies, since BookTravelButton renders on deeplinkable travel routes

Deliberate:

  • Design label / @Expensify/design — this change alters no UI, so I did not tag the design team. I checked the parent conditional because it doesn't apply, but left this sub-item unchecked rather than claim an action I didn't take.

Also worth knowing

test (job 1) is still red. It's the pre-existing SearchUIUtilsTest failure on main, tracked as #100888 — unrelated to this diff, as covered in my earlier comment. Merge main in once that lands.

What I verified locally about the fix itself is in this comment: the regression test fails on the pre-fix code, and a phone-number primary login is still correctly blocked.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.

Run details

dylanexpensify
dylanexpensify previously approved these changes Sep 11, 2026

@dylanexpensify dylanexpensify left a comment

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.

Looks good from a product perspective 👍

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.

Run details

@daledah

daledah commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please check this codex comment

account.primaryLogin is sometimes written as an empty string rather than
being absent. Because '' is not nullish, ?? never fell through to the
session email, so the hook returned '' and BookTravelButton still showed
the contact-method error for those users.

Use || so an empty primaryLogin falls back to the session email, matching
the existing precedent in VerifyAccountPageBase.

Co-authored-by: daledah <daledah@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.

Run details

@melvin-bot
melvin-bot Bot requested review from carlosmiceli and removed request for a team September 11, 2026 09:26
@melvin-bot

melvin-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

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

Copy link
Copy Markdown
Contributor Author

No code fix needed — the last red check is a human review gate, not a failure in your code. I pushed nothing.

On the current head (22fa38d4), all 8 test shards pass, along with typecheck, ESLint, Oxfmt, spellcheck, React Compiler Compliance, Storybook, and both perf jobs.

The only remaining red check is Check independent approval, which fails with:

approvers: [],
independentApprovers: [],
Error: Expensify/App#100931 does not have enough independent eligible reviewer approvals.

Nobody has approved the PR yet. That clears when an eligible reviewer approves — there is no commit that can fix it.

What changed since my last comment

One thing worth a reviewer's eye: that change edits a shared hook, not just the travel button. Widening the fallback there also affects the card and validate-code pages that call usePrimaryContactMethod. For those callers an empty primaryLogin falling back to the session email looks correct, but it is a wider blast radius than the original one-component fix.

Still outstanding for a human: the Tests and QA Steps sections in the PR body are placeholders, and the PR Author Checklist is unchecked. I could not drive NewDot in a browser on this run, so the travel flow itself remains unverified by me — everything above is from CI logs and the diff.


view run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Melvin-Test-Android Melvin-Test-Web Triggers Melvin to run the testing steps of the PR on web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants