Skip to content

Replace work email input with button linking to Contact Methods in card feed flows - #95610

Open
MelvinBot wants to merge 22 commits into
mainfrom
claude-cardFeedAddWorkEmailButton
Open

Replace work email input with button linking to Contact Methods in card feed flows#95610
MelvinBot wants to merge 22 commits into
mainfrom
claude-cardFeedAddWorkEmailButton

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

The workspace company cards and Expensify Card "import a feed from another workspace" flows both showed an inline email input on the Add work email screen and called AddWorkEmail directly on submit. That command is an account-takeover vector and is now rejected by the backend for validated callers, so the screen silently failed on submit.

This PR replaces the inline email input on both card‑feed Add work email screens with an "Add work email" button. The screen keeps the same title and description text, but the button now navigates the user to Settings → Profile → Contact Methods (SETTINGS_CONTACT_METHODS), where adding a work email goes through the standard magic‑code‑verified flow. The backTo param is set to the current route so the user returns to the card‑feed screen afterward.

Files changed:

  • src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx
  • src/pages/workspace/expensifyCard/WorkspaceExpensifyCardAddWorkEmailPage.tsx
  • src/languages/en.ts / src/languages/es.ts — added workspace.companyCards.addWorkEmailButton ("Add work email").

The now-unused AddWorkEmail inline submit/validation logic was removed from these two screens.

🤖 This is an automated PR created by MelvinBot on behalf of madmax330.

Fixed Issues

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

PROPOSAL:

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review"

  • Verify that no errors appear in the JS console

  • Create a new gmail account

  • Create a control workspace

  • Add a Mock Bank feed

  • Create a new workspace

  • Try to add the previously created Mock Bank feed:

Screenshot 2026-07-22 at 4 33 08 PM
  • Make sure you're asked to add a work email with a button:
Screenshot 2026-07-22 at 4 33 15 PM
  • Click the button and make sure it takes you to the new contact method screen
Screenshot 2026-07-22 at 4 33 33 PM

Offline tests

  • Verify that no errors appear in the JS console

QA Steps

// TODO: The human co-author must fill out the QA tests you ran before marking this PR as "ready for review".

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

…rd feed flows

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team July 8, 2026 14:56
@OSBotify

This comment has been minimized.

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/ONYXKEYS.ts 100.00% <ø> (ø)
...DynamicNewContactMethodConfirmValidateCodePage.tsx 0.00% <ø> (ø)
...ngs/Profile/Contacts/DynamicContactMethodsPage.tsx 77.77% <80.00%> (+0.35%) ⬆️
...s/Profile/Contacts/DynamicNewContactMethodPage.tsx 0.00% <0.00%> (ø)
...panyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx 0.00% <0.00%> (ø)
...ifyCard/WorkspaceExpensifyCardAddWorkEmailPage.tsx 0.00% <0.00%> (ø)
... and 482 files with indirect coverage changes

…heck

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the failing Compare knip issues against main check.

Cause: This PR removed the inline email form from both card‑feed Add work email screens, which left src/types/form/AddWorkEmailForm.ts with an unused default export (INPUT_IDS). Knip flagged it as a new finding (+1).

Fix: Removed the now-unused export default INPUT_IDS; line. INPUT_IDS is still referenced internally within the file (in the AddWorkEmailForm type definition), and the named AddWorkEmailForm type export is still used by ONYXKEYS.ts, so nothing else breaks. Verified via grep that no other file imports the default export.

The only other failing check is checklist (PR Author Checklist), which is a human‑only checklist and not affected by code changes.

⚠️ Note: I couldn't run knip/typecheck locally in this environment (blocked commands), so I relied on the fact that knip named this exact export and confirmed the export is otherwise unreferenced. CI will confirm.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ The Compare knip issues against main check now passes after my previous fix (removed the unused default export from AddWorkEmailForm.ts).

The only remaining failing check is PR Author Checklist / checklist — that's a human-only checklist, not a code/CI failure. There's nothing for me to push here; it clears once the checklist items in the PR description are completed by the author.

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the failing typecheck check.

Cause: The PR added the new workspace.companyCards.addWorkEmailButton key to en.ts and es.ts, but not to the other 8 locales. Since TranslationDeepObject requires every locale to define the same keys, tsc failed with TS2741: Property 'addWorkEmailButton' is missing for de, fr, it, ja, nl, pl, pt-BR, and zh-hans.

Fix: Added addWorkEmailButton to all 8 remaining locale files, reusing each locale's existing "Add work email" button translation (the top‑level workspace.companyCards.addWorkEmail label), placed right after addWorkEmailDescription to match the en/es structure.

The knip check from the earlier fix now passes. The only other failing check is checklist (PR Author Checklist), a human‑only checklist unaffected by code.

⚠️ Note: I couldn't run tsc locally in this environment (blocked commands), but the change adds exactly the missing key that tsc named in each locale. CI will confirm.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ The knip fix I pushed earlier worked — Compare knip issues against main now passes.

The only remaining failing check is checklist (the PR Author Checklist), which fails because the checkboxes in the PR body haven't been completed. That's a human-only checklist — I can't tick those on your behalf. To clear it, fill out the Tests / QA Steps and check the boxes in the PR description, then it will pass.

All other checks are passing or still running.

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the failing ESLint check (3 errors). The only other failing check is checklist (PR Author Checklist), which is human-only and unaffected by code.

What was wrong & fixed:

  1. Deprecated Button import in both card-feed Add work email pages — no-restricted-imports forbids the legacy @components/Button. Switched both to the composed @components/ButtonComposed and adapted the JSX to its API (variant="success", size={CONST.BUTTON_SIZE.LARGE}, and a <Button.Text> child instead of success/large/text props):

  2. import/prefer-default-export on AddWorkEmailForm.ts — this PR removed the inline email form, so that form type became fully dead code. My earlier knip fix (removing its now-unused default export) left it with a single named export, tripping this rule. Since the form is no longer used anywhere, I removed it entirely — the type file plus its ONYXKEYS FORMS entries and types/form/index.ts re-export. This resolves both eslint and the original knip finding at once.

Details
  • The AddWorkEmail form (addWorkEmailForm / addWorkEmailFormDraft Onyx keys + AddWorkEmailForm type) was only self-referenced in the forms registry after this PR removed the inline input; no component used it. Removed cleanly, matching the sibling form registration pattern.
  • Local eslint/typecheck/knip binaries are blocked in this run's environment, so props were verified against ButtonComposed/types.ts and an existing usage; CI will confirm.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ The Compare knip issues against main check I fixed earlier is now passing.

The only remaining failing check is PR Author Checklist / checklist. This is not a code issue — it fails because the checklist items in the PR body (Tests / QA / self-review boxes) haven't been completed. Those are human-only sections that I intentionally don't fill out on your behalf.

To clear it: fill out the ### Tests and ### QA Steps sections and check the boxes in the PR description, then the checklist check will pass.

No further code fix is needed from me.

@madmax330

Copy link
Copy Markdown
Contributor

@MelvinBot can you merge main?

…kEmailButton

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ Merged main into claude-cardFeedAddWorkEmailButton and pushed.

The merge was clean — no conflicts. Merge commit: 36026fba143.

Note: I wasn't able to verify anything in the browser this run (web environment unavailable), so this only confirms the git merge itself is clean, not runtime behavior.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ MelvinBot CI Fix Loop Detected

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.

@madmax330
madmax330 marked this pull request as ready for review July 22, 2026 14:38
@madmax330
madmax330 requested a review from a team as a code owner July 22, 2026 14:38
@melvin-bot
melvin-bot Bot requested review from gijoe0295 and removed request for a team July 22, 2026 14:38
@melvin-bot

melvin-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

@gijoe0295 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 trjExpensify and removed request for a team July 22, 2026 14:38

@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: 36026fba14

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

Comment thread src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx Outdated
@gijoe0295

Copy link
Copy Markdown
Contributor

Is this a blocker? Otherwise can I review it tomorrow? Thanks

trjExpensify
trjExpensify previously approved these changes Jul 22, 2026

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

Fair enough 👍

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Not quite — two checks are red. Both are fixable and neither is a problem with the actual change in this PR.

1. ESLint check — 4 errors (@typescript-eslint/no-unnecessary-type-assertion)

They're all in company-card files this PR doesn't intentionally touch:

These ... as CompanyCardFeedWithDomainID casts don't exist on main — the branch is exactly 1 commit behind, and that commit (#99247) made getCardFeedWithDomainID return the precise type and removed these now-redundant casts. The stale copies on this branch are what's tripping the rule.

Fix: merge the latest main into the branch and the errors clear.

2. Bun tests — flaky timeout

Only generateTranslations > full translations > translates nested structures failed, on a 5006ms > 5000ms timeout — a cold-start flake, not an assertion failure. There's already a Re-trigger CI to clear flaky Bun test cold-start timeout commit, but it timed out again on the latest run, so it just needs another re-run (or a small timeout bump).

Also: the QA Steps section is still the // TODO placeholder — worth filling before merge. (The Tests section and screenshots are already done.) Check independent approval being red is expected — that just needs a human reviewer.

Once ESLint is green and Bun passes, it's ready for review.


view run

variant="success"
size={CONST.BUTTON_SIZE.LARGE}
// After the user adds their work email, the back button should take them to the feed selector to pick a feed again, not back to this now-stale prompt page.
onPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID)))}

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 Contact methods route was refactored to dynamic routes DYNAMIC_ROUTES.CONTACT_METHODS.

Also DynamicContactMethodsPage doesn't respect the backTo param:

onBackButtonPress={() => Navigation.goBack()}

Please fix.

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.

No change needed here: both points landed in later commits on this branch.

-- AI Maxence

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Good catches — both are real. Here's the concrete fix for each.

1. Navigate via the dynamic route, not ROUTES.SETTINGS_CONTACT_METHODS

Contact methods is now the dynamic route DYNAMIC_ROUTES.CONTACT_METHODS (path contact-methods, ROUTES.ts:112), rendered by DynamicContactMethodsPage. Build the URL with createDynamicRoute(suffix, basePath), which appends /contact-methods onto the card‑feed path so back-nav returns there.

(createDynamicRoute / DYNAMIC_ROUTES are already imported in the Expensify Card page; add the imports to the Company Card page.)

2. Make DynamicContactMethodsPage respect the back path

DynamicContactMethodsPage.tsx:66 hardcodes Navigation.goBack(), which ignores where it was launched from. Mirror the sibling DynamicContactMethodDetailsPage, which strips its own suffix off the URL and passes the result to goBack (DynamicContactMethodDetailsPage.tsx:81, :255):

const backTo = useDynamicBackPath(DYNAMIC_ROUTES.CONTACT_METHODS.path);
// …
onBackButtonPress={() => Navigation.goBack(backTo)}

useDynamicBackPath removes the trailing /contact-methods from the current URL, yielding the card‑feed screen; goBack(backTo) uses it as the fallback when there's no back-stack entry.

Note: this touches DynamicContactMethodsPage, which is shared by every entry into the contact‑methods list — worth a quick check that the normal Settings → Profile → Contact Methods back button still behaves after the change.


I can only make code changes when directed by the PR author, an assignee, or an existing co-author. To have me implement this, the PR assignee or author (madmax330) should reply with a request tagging me.


view run

@madmax330

Copy link
Copy Markdown
Contributor

@MelvinBot can you make these changes? #95610 (comment)
Also merge main to fix some of the failing tests

MelvinBot and others added 2 commits August 25, 2026 11:13
…kEmailButton

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
…ions

The branch was behind main, which narrowed getCardFeedWithDomainID's return
type so the 'as CompanyCardFeedWithDomainID' casts became redundant. On the CI
merge ref these tripped @typescript-eslint/no-unnecessary-type-assertion.
Remove the four casts (and the now-unused import in CardInstructionsStep) to
match main.

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Pushed a fix for the failing ESLint check. The other failing checks are the checklist bots (ignored) and a flaky Bun test (details below).

Root cause (ESLint): This branch had fallen behind main. On main, getCardFeedWithDomainID's return type was narrowed so four as CompanyCardFeedWithDomainID casts became redundant — and main already removed them. Since CI lints the merge ref (branch + main), those stale casts tripped @typescript-eslint/no-unnecessary-type-assertion in three files this PR doesn't otherwise touch:

Fix: Removed the four redundant casts to match main, and dropped the now-unused CompanyCardFeedWithDomainID import in CardInstructionsStep.tsx (also matching main). Each edit is byte-identical to main's current version of those lines.

Other failing checks
  • Bun tests — flaky cold-start timeout, unrelated to this PR: generateTranslations > full translations > translates nested structures timed out at 5006ms (6ms over the 5000ms limit) while spawning oxfmt --write. Your previous commit (cbc0dd0d) was already a re-trigger for this same cold-start flake. This push re-runs it.
  • Check independent approval / checklist — bot/checklist checks on the ignore list; not code issues.

ℹ️ Note: this branch is behind main beyond these four lines (e.g. CompanyCards.ts has an unrelated assignWorkspaceCompanyCard refactor on main). I scoped this fix to just the failing check; a git merge main when you next touch the PR would fully resync it.


view run

MelvinBot and others added 2 commits August 25, 2026 11:19
Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
…n' into claude-cardFeedAddWorkEmailButton

Co-authored-by: Maxence Coulibaly <madmax330@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — merged main and pushed both fixes from that comment. New head is 6f8848e.

1. Navigate via the dynamic route (DYNAMIC_ROUTES.CONTACT_METHODS) instead of ROUTES.SETTINGS_CONTACT_METHODS, so /contact-methods is appended onto the card-feed path:

2. DynamicContactMethodsPage respects the back path — mirrors the sibling DynamicContactMethodDetailsPage using useDynamicBackPath instead of a hardcoded goBack():

Merge / checks: merging main cleared the ESLint failures (the stale as CompanyCardFeedWithDomainID casts were removed on main via #99247). lint-changed, typecheck, and spell-changed all pass locally; CI is re-running the full suite (including React Compiler compliance, which I couldn't run locally in this env but which the change satisfies — it only adds a hook call, no manual memoization).

Worth a manual check before merge (I couldn't drive the full flow — it needs a public-domain account with a mock feed across two workspaces, and this change touches a shared page):

  • Card-feed flow: add work email → Contact Methods → back returns to the feed selector.
  • Regression: normal Settings → Profile → Contact Methods back button still returns to Profile.

view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ All the real checks are green now — nothing left to fix here.

  • ESLint check — now passes. My earlier fix (removing the four stale as CompanyCardFeedWithDomainID casts) is merged into the current HEAD (6f8848e0), and I confirmed 0 of those casts remain. Combined with your main merge, the check is clean.
  • Bun tests — now passes. The earlier failure was the flaky generateTranslations cold-start timeout, confirmed by two green re-runs.

The only remaining red check is Check independent approval, which isn't a code issue — it just needs a human reviewer's approval.


view run

@madmax330

madmax330 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Alright @gijoe0295 ready again

@gijoe0295

Copy link
Copy Markdown
Contributor

@MelvinBot The changes didn't work. After adding the new contact contact-methods/new-contact-method, I tap back to Contact methods page DynamicContactMethodsPage (the URL shows contact-methods/contact-methods which is weird). Tapping back from this page is not responsive.

Screen.Recording.2026-08-26.at.23.29.46.mov

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed repro gijoe0295 — you're right, and I can explain why.

Root cause

The bug is in the dynamic-routes machinery, not in the copy/button change. Both card‑feed screens launch Contact Methods with a contact-methods dynamic route mounted on top of a workspace RHP base:

Going through the add‑contact‑method flow (which forceReplaces several transient steps) leaves the state with a redundant DYNAMIC_CONTACT_METHODS node in the settings RHP stack. Two confirmed mechanisms then produce exactly what you saw:

  1. contact-methods/contact-methods URLgetPathFromState rebuilds the dynamic URL structurally: it appends each dynamic screen's configured path (contact-methods) onto the recursively-resolved base and ignores the leaf's stored full path (getPathFromState.ts:170). With the node present twice, the base already ends in contact-methods and a second one gets concatenated.

  2. Dead back buttonDynamicContactMethodsPage computes backTo = useDynamicBackPath(CONTACT_METHODS.path), which strips exactly one contact-methods (getPathWithoutDynamicSuffix.ts:18) → target …/contact-methods, which resolves to the same page. goBack then computes distanceToPop === 0 and returns without dispatching (Navigation.ts:538) — hence unresponsive.

Recommended fix

The clean fix is to stop mounting the Contact Methods dynamic chain over a foreign workspace RHP base. Options, most contained first:

  • Don't launch via entryScreens: ['*'] here — give the contact-methods dynamic route a Settings-rooted base (as the Expensify Card analog effectively should), or dedupe in getStateForDynamicRoute / getDynamicRouteAdaptedState so a DYNAMIC_CONTACT_METHODS base is never inserted beneath an existing one.
  • Harden getPathFromState (:170) to not re-append a suffix the base already ends with — fixes the doubled URL regardless of how the redundant node arises.
  • Harden back-path — strip all trailing repeats in getPathWithoutDynamicSuffix, and/or in goUp fall back to a real pop when distanceToPop <= 0 but the focused route equals the target.
Notes & confidence
  • Confidence medium-high: the two mechanisms above (URL reconstruction and the distanceToPop === 0 dead button) are confirmed by static tracing. What I could not pin purely statically is the exact forceReplace/goBack dispatch that inserts the second DYNAMIC_CONTACT_METHODS node — every happy-path trace yielded a single node, so it emerges from a state-adaptation nuance (prime suspect: insertStateBelow in getDynamicRouteAdaptedState.ts inserting a contact-methods base below an already-present one). A quick runtime navigationRef.getRootState() dump at the moment the doubled URL appears will confirm which dispatch to target and therefore which of the fixes above is minimal.
  • The static SETTINGS_CONTACT_METHODS route is no longer wired to a screen — the list is now only the dynamic DYNAMIC_CONTACT_METHODS screen — so "just navigate to the static route" isn't available; the fix has to live in the dynamic-route layer.

I haven't pushed a change: I can only make code changes when directed by the PR author, an assignee, or an existing co-author. madmax330 — if you'd like me to implement one of the fixes above, reply tagging me and I'll take a pass (a runtime state dump first would let me pick the minimal one).


view run

@gijoe0295

Copy link
Copy Markdown
Contributor

Hi @madmax330 can you guide Melvin to continue the work here? The fix didn't work.

@madmax330

Copy link
Copy Markdown
Contributor

Yeah I will work with Claude on it locally

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

No code fix needed — every real check is green on f33ac0aa. 38 pass, 1 skip, 1 fail, and the one failure is Check independent approval, which just needs a human reviewer's approval. It isn't a code problem, and I can't clear it by pushing.

The two checks that were red on earlier runs of this branch are now passing on your commit:

Check Result
ESLint check ✅ pass (9m22s)
Bun tests ✅ pass (26s)
Jest Unit Tests (jobs 1–8) ✅ pass
TypeScript Checks ✅ pass
Reassure Performance Tests ✅ pass
Remote Build iOS / Android ✅ pass
Why nothing was pushed

When I looked, ESLint check, baseline-perf-tests, and branch-perf-tests were still in progress on run 33669819685 and 33669819628, so I waited for them rather than guessing. All three finished green.

The only failing job is the Verify peer review step in Check independent approval. That workflow gates on a reviewer approving the PR independently of its authors — no branch change affects it.

Separately: your f33ac0aa ("Fix contact methods back navigation", touching DynamicNewContactMethodPage.tsx and DynamicNewContactMethodConfirmValidateCodePage.tsx) is the first commit that targets the dead-back-button behavior gijoe0295 reported. CI can't validate that — it needs a manual re-run of the repro. I did not verify it in this run, since you asked me to look at the checks.


view run · no recording available

@madmax330

Copy link
Copy Markdown
Contributor

Ok @gijoe0295 it's working now

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Nothing to fix — every real check is green on 5fb257a8. 40 pass, 1 skip, 1 fail, and the only failure is Check independent approval (its Verify peer review step). That's the human-reviewer gate, not a code problem, so no push can clear it.

What I watched, and why it took a while

HEAD moved twice while I was waiting, so CI restarted from scratch each time:

Commit Author Change
f33ac0aa madmax330 Fix contact methods back navigation
15e3c3f7 madmax330 Fix duplicate contact methods back path
5fb257a8 madmax330 Merge main

On the final HEAD I waited for the full suite rather than reporting early. All of these came back green: ESLint, typecheck, Bun tests, Jest (jobs 1–8), Knip, Storybook, React Compiler Compliance, unused styles, spellcheck, generateTranslations, baseline/branch perf tests, and all four native builds.

The one red job is Check independent approval. It gates on a reviewer approving independently of the PR's authors — no branch change affects it.

On the actual bug: your 15e3c3f7 dedupes the repeated contact-methods suffix in DynamicContactMethodsPage's back path using the new findAllMatchingDynamicSuffixes.ts helper, which targets the distanceToPop === 0 dead-back-button mechanism I described earlier. CI cannot validate that — it needs a manual re-run of gijoe0295's repro. I did not verify it in this run, since you asked me to look at the checks.


view run · no recording available

@madmax330
madmax330 requested a review from gijoe0295 September 7, 2026 15:42
@gijoe0295

gijoe0295 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

This concern is not resolved yet #95610 (comment). Navigating back from contact methods page still resolves to Add work email prompt:

Screen.Recording.2026-09-08.at.10.33.32.mov

I think we might need to add an effect inside WorkspaceCompanyCardAddWorkEmailPage to automatically navigate to feed selector like this:

Diff
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx
index 45cf9e0a273..8c5dead6149 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx
@@ -5,21 +5,27 @@ import ScreenWrapper from '@components/ScreenWrapper';
 import Text from '@components/Text';
 
 import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import usePrimaryContactMethod from '@hooks/usePrimaryContactMethod';
 import useThemeStyles from '@hooks/useThemeStyles';
+import { isEmailPublicDomain } from '@libs/LoginUtils';
 
 import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
+import { expensifyLoginsSelector } from '@libs/UserUtils';
 
 import Navigation from '@navigation/Navigation';
 import type {SettingsNavigatorParamList} from '@navigation/types';
 
 import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
+import { useFocusEffect } from '@react-navigation/core';
 
 import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES';
 import type SCREENS from '@src/SCREENS';
 
-import React from 'react';
+import React, { useCallback } from 'react';
 import {View} from 'react-native';
 
 type WorkspaceCompanyCardAddWorkEmailPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.COMPANY_CARD_ADD_WORK_EMAIL>;
@@ -28,6 +34,23 @@ function WorkspaceCompanyCardAddWorkEmailPage({route}: WorkspaceCompanyCardAddWo
     const {policyID} = route.params;
     const {translate} = useLocalize();
     const styles = useThemeStyles();
+    const primaryContactMethod = usePrimaryContactMethod();
+    const [loginList] = useOnyx(ONYXKEYS.LOGINS, {selector: expensifyLoginsSelector});
+
+    const isUserFromPublicDomain = isEmailPublicDomain(primaryContactMethod);
+
+    useFocusEffect(useCallback(() => {
+        if (isUserFromPublicDomain) {
+            return;
+        }
+        const primaryLoginKey = primaryContactMethod ? Object.keys(loginList ?? {}).find((login) => login.toLowerCase() === primaryContactMethod.toLowerCase()) : undefined;
+        const isPrimaryContactValidated = primaryLoginKey ? !!loginList?.[primaryLoginKey]?.validatedDate : !primaryContactMethod;
+        if (!isPrimaryContactValidated) {
+            return;
+        }
+
+        Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.path, ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID)), {forceReplace: true});
+    }, [isUserFromPublicDomain, primaryContactMethod, loginList, policyID]));
 
     return (
         <AccessOrNotFoundWrapper

POC:

Screen.Recording.2026-09-08.at.11.58.20.mov

We might still need further polishes.

}}
onClose={() => {
Navigation.goBack(listPath);
// Contact Methods is already underneath this screen, so pop it instead of resolving its dynamic route again.

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.

Suggested change
// Contact Methods is already underneath this screen, so pop it instead of resolving its dynamic route again.

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.

Fixed in e62cd8f — comment removed.

-- AI Maxence

const onBackButtonPress = useCallback(() => {
Navigation.goBack(listPath);
}, [listPath]);
// Contact Methods is already underneath this screen, so pop it instead of resolving its dynamic route again.

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.

Suggested change
// Contact Methods is already underneath this screen, so pop it instead of resolving its dynamic route again.

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.

Fixed in f1cc739 — comment removed.

-- AI Maxence

const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector});
const {isAccountLocked} = useLockedAccountState();
const {showLockedAccountModal} = useLockedAccountActions();
// Strip the `contact-methods` suffix off the current URL so the back button returns to wherever this list was launched from, not a hardcoded default.

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.

Suggested change
// Strip the `contact-methods` suffix off the current URL so the back button returns to wherever this list was launched from, not a hardcoded default.
// Strip the `contact-methods` suffix off the current URL so the back button returns to wherever this list was launched from

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.

Fixed in 7de0a14 — applied your wording.

-- AI Maxence

// Strip the `contact-methods` suffix off the current URL so the back button returns to wherever this list was launched from, not a hardcoded default.
const backPath = useDynamicBackPath(DYNAMIC_ROUTES.CONTACT_METHODS.path);
const repeatedContactMethodsSuffix = findAllMatchingDynamicSuffixes(backPath).find((match) => match.pattern === DYNAMIC_ROUTES.CONTACT_METHODS.path);
// Returning from a nested contact-method screen can leave a duplicate Contact Methods route in the stack. Remove it so Back targets the real parent instead of this screen.

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.

Suggested change
// Returning from a nested contact-method screen can leave a duplicate Contact Methods route in the stack. Remove it so Back targets the real parent instead of this screen.
// Remove duplicate Contact Methods route when returning from a nested contact-method screen

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.

Fixed in ca4e25a — applied your wording.

-- AI Maxence

@MelvinBot

MelvinBot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

No code fix to push — nothing is broken in CI. On ca4e25a0: 40 pass, 1 skip, 2 fail, and both failures are human-reviewer gates that no commit can clear.

Failing check Why it's red
Verify peer review / Check independent approval The PR has no independent approval yet (reviewDecision: REVIEW_REQUIRED). Clears when a reviewer approves.
PR Reviewer Checklist / checklist The job errored with No PR Reviewer Checklist was found — it scanned all 58 comments and none is the reviewer checklist. Clears when the reviewer posts and completes it.

Every real check is green, including the ones that were red on earlier runs of this branch: ESLint, Bun tests, typecheck, all 8 test jobs, spellcheck, Oxfmt, React Compiler Compliance, knip, both perf-test jobs, and all four builds.

What I checked
  • gh pr checks 95610 polled until every pending check resolved — ESLint check, baseline-perf-tests, branch-perf-tests, and the four build (...) jobs all finished green.
  • gh run view 34226693698 --log-failed on the reviewer-checklist job: the only error line is ##[error]No PR Reviewer Checklist was found, after it enumerated comments 1–58.
  • The Check independent approval job belongs to the Verify peer review for Expensify/App#95610 workflow, which is the approval gate, not a build or test.
  • Authorization: I'm the PR author and madmax330 is an assignee, so I could push — there's just nothing to push.

One thing worth flagging, since it's the actual blocker rather than CI: gijoe0295 reopened an unresolved concern on 2026-09-08 — navigating back from Contact Methods still lands on the Add work email prompt, with a suggestion to add an effect in WorkspaceCompanyCardAddWorkEmailPage that auto-navigates to the feed selector. That's a behavior change, not a check failure, so I left it alone. Reply with @MelvinBot fix the back-navigation concern if you want me to implement it.


view run · no recording available

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.

6 participants