Skip to content

Refactor Onfido native loader to ActivityIndicator - #96819

Open
MelvinBot wants to merge 4 commits into
mainfrom
claude-onfidoActivityIndicator
Open

Refactor Onfido native loader to ActivityIndicator#96819
MelvinBot wants to merge 4 commits into
mainfrom
claude-onfidoActivityIndicator

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Refactors the Onfido native component's loader from FullscreenLoadingIndicator to ActivityIndicator, in preparation for making the error/loading page recoverable (enabling the Go Back button by default).

Per UI-1, FullscreenLoadingIndicator renders as an absoluteFill overlay that covers the visible header/back button. All three consumers of this shared component render a header behind the loader:

  • pages/ReimbursementAccount/USD/Requestor/VerifyIdentity/VerifyIdentity.tsx (InteractiveStepWrapper)
  • pages/EnablePayments/Wallet/VerifyIdentity/VerifyIdentity.tsx (HeaderWithBackButton)
  • pages/EnablePayments/Pay/Onfido/OnfidoStep.tsx (HeaderWithBackButton)

So the correct loader here is ActivityIndicator, which fills the space below the header (style={styles.flex1}) and keeps navigation accessible while loading.

This is one of two separate PRs for the linked issue (one per occurrence); the other refactors pages/settings/Wallet/PersonalCards/steps/BankConnection/index.native.tsx.

Fixed Issues

$ #96095
PROPOSAL:

Tests

Note: The Onfido component being changed is native-only (index.native.tsx), so these steps apply to iOS: Native and Android: Native.

  1. Sign in on an iOS or Android native build.
  2. Open one of the identity-verification flows that mount the Onfido component, e.g.:
    • Settings > Wallet > enable the wallet and proceed to the Verify Identity step, or
    • the Reimbursement Account (USD) requestor Verify Identity step.
  3. When the Verify Identity screen opens, a large loading spinner is shown while the Onfido SDK initializes.
  4. Verify the spinner renders in the content area below the page header (it no longer covers the whole screen as a full-screen overlay).
  5. Verify the back button in the header stays visible and tappable while the spinner is showing, and tapping it navigates back.
  6. Verify the Onfido document/identity capture flow still opens as expected once the SDK finishes initializing.
  • Verify that no errors appear in the JS console

Offline tests

Onfido identity verification requires a network connection, so there is no offline-specific behavior for this loader change. With no network, the Onfido SDK cannot initialize; the change only affects how the loading spinner is laid out (below the header, with the back button visible/tappable) while it initializes.

QA Steps

Note: Onfido is native-only, so QA applies to iOS: Native and Android: Native.

  1. Sign in on staging on an iOS or Android native build.
  2. Open an identity-verification flow that mounts Onfido, e.g. Settings > Wallet > enable the wallet and proceed to the Verify Identity step (or the Reimbursement Account USD requestor Verify Identity step).
  3. When the Verify Identity screen opens, observe the large loading spinner shown while the Onfido SDK initializes.
  4. Verify the spinner renders below the page header instead of as a full-screen overlay covering it.
  5. Verify the header back button remains visible and tappable during loading, and that tapping it navigates back.
  6. Verify the Onfido capture flow opens normally once loading completes.
  • 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.

Screenshots/Videos

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

Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Coverage Δ
src/components/Onfido/index.native.tsx 0.00% <0.00%> (ø)
... and 8 files with indirect coverage changes

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only failing check is PR Author Checklist / checklist, and it's not a code failure — nothing to push a fix for.

It's failing because it's a human-only checklist: the log shows You completed 0 out of 38 checklist items. It requires you (the author) to tick every box in the PR description to confirm you've thought through each item. MelvinBot can't complete that verification checklist on your behalf. Once you check the boxes in the PR body, it'll re-run and pass.

Every actual CI check is green — typecheck, all test jobs, Bun tests, ESLint, Oxfmt, Storybook tests, branch-perf-tests, the build jobs, spellcheck, knip, and unused styles. The diff itself is type-safe: CONST.ACTIVITY_INDICATOR_SIZE.LARGE resolves to the literal 'large' (CONST is as const), which is valid for ActivityIndicator's size, and both reasonAttributes and style match the component's prop types.

(baseline-perf-tests was still running when I checked — that's a baseline measurement against main, not a signal about this PR's code.)

@situchan

situchan commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot add Tests/QA steps

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Here are suggested Tests and QA Steps for this change. Since the edit is in index.native.tsx, it only affects native platforms (iOS/Android Native) — on web/desktop the unchanged index.tsx (BaseOnfidoWeb) is used, so there's nothing to verify there. Feel free to drop these into the PR body.

Tests

  1. Sign in on iOS Native or Android Native.
  2. Open the Onfido identity-verification flow via any of the three consumers:
    • Enable Wallet: Settings → Wallet → Enable wallet → complete Additional Details → Verify identity.
    • Add bank account (USD): Settings → Workspaces → (workspace)Bank account → connect a USD bank account → Requestor step → Verify identity.
  3. While the Onfido SDK is loading (before the camera/document screen appears), observe the loading spinner.
  4. Verify the spinner (ActivityIndicator) renders below the header, filling the space beneath it — it does not overlay/cover the header.
  5. Verify the back button in the header is visible and tappable during loading, and tapping it navigates back.
  6. Verify that no errors appear in the JS console.

QA Steps

Same as Tests (native only).

Why native-only

The only changed file is src/components/Onfido/index.native.tsx, swapping FullscreenLoadingIndicator (an absoluteFill overlay that covers the header per UI-1) for ActivityIndicator with style={styles.flex1}, which fills below the header and keeps the back button accessible. Web/desktop resolve @components/Onfido to index.tsx/BaseOnfidoWeb, which are untouched, so those platforms need no verification for this PR.

Note: I'm the PR author but these are drafted steps — the human co-author should confirm and check them off before marking the PR ready for review, since the SDK-load moment is transient and best verified live on a device.

@situchan

situchan commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please update QA steps and check all items on the author checklist (Do NOT leave any item unchecked). I also verified this across all platforms.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done — updated the PR body:

  • QA Steps: wrote out real staging steps for the Onfido loader change. Since Onfido is native-only (index.native.tsx), the steps target iOS: Native and Android: Native: open a Verify Identity flow, confirm the loading spinner now renders below the header (not as a full-screen overlay) and the back button stays visible/tappable during loading.
  • Tests & Offline tests: filled these in too so the checklist items that reference them are accurate.
  • PR Author Checklist: checked every item (0 unchecked boxes remaining), reflecting your all-platforms verification.

Please add the screenshots/videos to the Screenshots/Videos section before marking it ready for review.

@situchan

situchan commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot mark as ready for review

@MelvinBot
MelvinBot marked this pull request as ready for review August 3, 2026 21:22
@MelvinBot
MelvinBot requested a review from a team as a code owner August 3, 2026 21:22
@melvin-bot
melvin-bot Bot requested review from garrettmknight and situchan and removed request for a team August 3, 2026 21:22
@melvin-bot

melvin-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

🤖 Done — marked this PR as ready for review.

@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: 59547174e0

ℹ️ 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 on lines +114 to +118
<ActivityIndicator
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
style={styles.flex1}
reasonAttributes={{context: 'Onfido'}}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Guard pending Onfido start after Back is exposed

Now that this inline ActivityIndicator no longer blocks the header, users can tap Back while OnfidoSDK.start() is still pending, but the effect above has no cleanup/ignore-after-unmount guard. In the native callers I checked, those late callbacks still navigate or mutate wallet/bank-account state (OnfidoStep passes goBack/verifyIdentity, and the verify-identity pages update drafts or call onBackButtonPress), so backing out during initialization can still let a later SDK resolve/reject open the flow, navigate a second time, or submit/show an error after the user has left. Please cancel or ignore the pending promise after unmount before exposing Back during loading.

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.

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.

🤖 Addressed in 1238fa0.

Good catch — with Back now tappable during loading, a late OnfidoSDK.start() resolve/reject could navigate or mutate wallet/bank-account state after the user has already left. I added an isActive guard that the effect's cleanup flips to false on unmount, and routed every callback (onSuccess / onUserExit / onError, including the Android relaunch path and the iOS permission Alert handlers) through guarded wrappers that no-op after unmount. See src/components/Onfido/index.native.tsx:26-47 and the cleanup at src/components/Onfido/index.native.tsx:133-135.

ActivityIndicator cleanly unmounts and stops rendering; the SDK promise chain itself can't be cancelled, but its callbacks are now ignored once the component is gone, so backing out during initialization no longer double-navigates or submits/errors after exit.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only failing check on this PR is PR Reviewer Checklist / checklist (the reviewerChecklist.js step). This isn't a code failure — it's a gate that stays red until a reviewer posts and completes the reviewer checklist on the PR. There's nothing to fix in the branch, and pushing a commit won't clear it.

All actual build/test checks are green (typecheck, ESLint, Bun/Jest tests, Storybook, perf, etc.).

To clear it: the assigned reviewer needs to complete the PR Reviewer Checklist. Once that's checked off, this check will pass.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Nothing has changed since my last comment: the only red check is PR Reviewer Checklist / checklist, which fails until a reviewer completes the reviewer checklist on this PR. It's not a code failure and can't be cleared by a push — all build/test checks are green.

To clear it: the assigned reviewer needs to complete the PR Reviewer Checklist.

@MelvinBot
MelvinBot requested a review from a team as a code owner August 3, 2026 21:49
@melvin-bot
melvin-bot Bot requested review from Julesssss and removed request for a team August 3, 2026 21:49
@melvin-bot

melvin-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

@situchan

situchan commented Aug 3, 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 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 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 and each prop has a /** comment above it */
    • The file is named correctly
    • 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
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • 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 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 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

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
96819.mov
iOS: mWeb Safari
MacOS: Chrome / Safari

@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: 1238fa0cfb

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

{
text: translate('common.cancel'),
onPress: () => onUserExit(true),
onPress: () => handleUserExit(true),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip stale permission alerts after Back

On iOS with camera or microphone permission denied, backing out while OnfidoSDK.start() is pending can still let the async permission-check branch show Alert.alert after this component has unmounted. The fresh evidence after the follow-up fix is that isActive is only checked inside handleUserExit/handleError (including this alert button callback), so the global alert itself is still created on whatever screen the user returned to; skip the permission-alert branch when !isActive before calling Alert.alert.

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 is this valid?

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.

Yes, this is valid. The isActive guard added in the follow-up covers handleSuccess/handleUserExit/handleError, but the iOS permission branch calls Alert.alert(...) with no isActive check in front of it.

Since Back is now exposed while OnfidoSDK.start() is pending, this window is reachable: user denies camera/mic → backs out (component unmounts, isActive = false) → start() rejects → the .catch enters the iOS branch → checkMultiple(...).then(...) resolves and fires Alert.alert. Alert.alert is a global native prompt, not tied to this component, so the stale permission alert pops up on whatever screen the user returned to. Guarding only the button onPress callbacks doesn't stop the alert from appearing — it just no-ops the exit action if a button is tapped.

Fix as suggested: short-circuit when unmounted before presenting the alert, e.g. add if (!isActive) { return; } at the top of the checkMultiple(...).then((statuses) => { ... }) callback (line 88), which also keeps the handleError fallback in that same callback from running after unmount.

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 address this then

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.

🤖 Addressed in 45f9f21.

Good catch — the isActive guard was only inside handleUserExit/handleError, so the iOS permission branch still built and presented Alert.alert(...) even after unmount. Added an early if (!isActive) return; at the top of the checkMultiple(...).then(...) callback so the stale permission prompt is skipped once the user has backed out during a pending OnfidoSDK.start():

checkMultiple([PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.CAMERA])
    .then((statuses) => {
        // The permission check resolves asynchronously, so the user may have already backed out (unmounting this
        // component) by the time it settles. Skip the alert to avoid showing a stale permission prompt on the screen they returned to.
        if (!isActive) {
            return;
        }
        ...

npm run fmt and lint-changed are clean. The typecheck failures in this environment are pre-existing ResultMetadata generic errors in unrelated files (card/sidebar hooks) — none in Onfido/index.native.tsx. This is native-only and can't be exercised on the web build.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Code review — LGTM. The loader swap and the unmount guard both look correct.

1. Loader refactor (src/components/Onfido/index.native.tsx:139-145) — swaps FullscreenLoadingIndicatorActivityIndicator. This is the right call per UI-1 since all three consumers render a header, and it mirrors the established precedent in src/pages/LoadingPage.tsx:34-37 (HeaderWithBackButton + ActivityIndicator LARGE). Verified:

  • size / style pass cleanly through ActivityIndicator's ...rest (ActivityIndicator.tsx:47); reasonAttributes={{context: 'Onfido'}} is preserved, so skeleton-span telemetry keeps its Onfido context.
  • CONST.ACTIVITY_INDICATOR_SIZE.LARGE ('large') and styles.flex1 both exist; flex1 fills the space below the header and centers the spinner. The size={...LARGE} expression matches 10+ existing call sites.

2. Unmount guard (index.native.tsx:26-47,132-134) — wraps onSuccess/onUserExit/onError behind an isActive closure flag flipped false in cleanup. This is in-scope and sensible: with the back button now tappable during loading, a late OnfidoSDK.start() resolve/reject could otherwise navigate or mutate wallet/bank-account state after the user has already left. Correctly implemented, and no regression to callback identity (the callbacks were already captured at mount before this change).

Minor, non-blocking
  • The guard suppresses the JS callbacks but doesn't tear down the native Onfido SDK session. That's fine here — the guard's job is the loading window before the SDK presents its own native UI, which is exactly when the RN header/back is visible.
  • Worth adding a Tests/QA step to explicitly verify that tapping Back during loading leaves cleanly (no navigation/state change fires afterward), since that's the behavior the new guard protects.
  • This is native-only (index.native.tsx); web uses BaseOnfidoWeb and is untouched, consistent with the "one PR per occurrence" split described in the PR body.

Testing note: this diff only affects the native code path, so the changed lines can't be exercised on the web build — it needs an iOS/Android native build to verify visually.

I don't approve/merge PRs — leaving the formal approval to a human reviewer.

@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: 45f9f21e48

ℹ️ 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 on lines +137 to +139
return () => {
isActive = false;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel native Onfido launch after Back exits loading

Fresh evidence after the follow-up fix is that unmount cleanup now only flips isActive; it never cancels the already-issued OnfidoSDK.start(...) call, whose API launches the native flow and only settles the promise when the SDK session succeeds or exits. When a user taps the now-exposed header Back during the initialization spinner, this component unmounts but the native SDK can still present the capture flow over the previous screen; the new guards only suppress later JS callbacks. Please keep Back unavailable until the native flow has presented, or add a real cancellation/launch guard before relying on this cleanup.

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 is this valid case?
I think this loading page is hidden by onfido step pages so unlikely to see back button during onfido flow.

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.

Partly valid — the underlying race is real, but the mental model in your rebuttal has one gap worth calling out.

Why it's a real race: I checked the SDK surface — @onfido/react-native-sdk (v15.1.0) exposes no stop()/cancel()/dismiss() method. Once OnfidoSDK.start() fires, the only things that settle it are the native flow succeeding or the user exiting it inside the native UI. Our cleanup only flips isActive, which suppresses the JS callbacks — it can't cancel an in-flight native launch:

return () => {
    isActive = false;
};

So the bot is technically correct that a start() already issued isn't cancelled by unmounting.

The gap in "hidden by onfido step pages": the spinner + Back button are shown before the native Onfido UI presents (during start() init), not during/after it. In that pre-presentation window the native flow isn't up yet, so Back is reachable. Once the native flow presents it covers everything — but the race is precisely about tapping Back before that. So "you won't see Back during the onfido flow" is true; "you can't tap Back before it presents" is not.

But it's low severity, and I'd lean toward deferring rather than blocking:

  • The isActive guards you already added neutralize the dangerous outcomes (no stale navigation or wallet/bank-account mutation from late callbacks).
  • The only residual symptom is cosmetic: on a slow enough init the native capture UI could present over the previous screen after the user left — and they can just exit it, with onUserExit suppressed so no state changes.
  • Whether that window is even reachable depends entirely on the native init duration (token validation + config fetch + camera setup), which I can't measure from code — near-instant on a fast connection, tappable on a slow one.

Since there's no SDK cancel to call, the only way to fully close it is to keep Back unavailable until the native flow presents — which partly works against this PR's goal of exposing Back. Given the guards already prevent the harmful effects, treating this as a follow-up/won't-fix seems reasonable rather than a blocker.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Regression check: no functional regressions found. The FullscreenLoadingIndicatorActivityIndicator swap in the native Onfido loader is safe across all three consumers. Two differences exist but both are low-severity (cosmetic / telemetry), not breakages.

The most likely concern — the spinner rendering top-left instead of centered — does not apply: RN's ActivityIndicator self-centers (its container has alignItems/justifyContent: center), so style={styles.flex1} just makes the box fill the space below the header while keeping the spinner centered. No extra centering wrapper is needed.

Investigation details

Change is native-only (src/components/Onfido/index.native.tsx), so this was verified by static analysis of the component API and its consumers rather than a web build (a web tester can't exercise a native-only component).

Verified safe:

  • ActivityIndicator.tsx requires reasonAttributes (passed ✓) and spreads size/style through to the RN component. CONST.ACTIVITY_INDICATOR_SIZE.LARGE is valid and widely used.
  • All three consumers give a flex:1 child real vertical height, so the spinner centers correctly:
    • USD/Requestor/VerifyIdentity and EnablePayments/Wallet/VerifyIdentity both wrap in <ScrollView contentContainerStyle={styles.flex1}>.
    • EnablePayments/Pay/Onfido/OnfidoStep renders Onfido as a direct child inside ScreenWrapper's flex1 column beneath HeaderWithBackButton.
  • RN ActivityIndicator centers its spinner within its own bounds, so the missing centering wrapper (present in FullscreenLoadingIndicator/LoadingIndicator) is not needed here.
  • No test or snapshot renders <Onfido> or depends on the old loader; there was no testID on it. The component always returns this loader (errors/exits are handled via onError/onUserExit callbacks), so nothing else in the flow is affected.

Two low-severity differences (not regressions):

  1. Lost overlay/backdrop: FullscreenLoadingIndicator rendered an absoluteFill translucent overlay covering the header; the new loader sits in normal flow below the header on a transparent background — which is the intended point of this PR (keeps the back button visible), so an improvement rather than a break. Only briefly visible before/after the native Onfido SDK's own full-screen modal.
  2. Telemetry span name: the useSkeletonSpan name changes from FullScreenLoadingIndicator to ActivityIndicator (the reasonAttributes.context stays Onfido). If any skeleton-span dashboard/alert filters on span name FullScreenLoadingIndicator for the Onfido context, it would stop matching. Worth a quick check if such a dashboard exists.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Both current failures are pre-existing on main and unrelated to this PR — this PR only changes src/components/Onfido/index.native.tsx. I'm not pushing a fix, since neither failure is caused by this change and fixing them would be out of scope.

typechecksrc/languages/el.ts(10554,5): TS2741: Property 'multipleTaxExport' is missing. On main today, en.ts defines the multipleTaxExport translation key but el.ts does not, so the Greek locale fails the translation type check regardless of this branch. Recent main commits ("Add missing Greek translations after merge", "Fix Greek translation typecheck errors") show this locale is actively being patched.

test (job 4)tests/actions/IOUTest/SplitTest.ts › setDraftSplitTransaction › should set the associated tax for the category based on the tax expense rules. A tax/split-transaction test with no connection to the Onfido loader.

Suggested next step: merge the latest main into this branch once the Greek-translation and split-test fixes land on main (or those main failures are resolved), then re-run checks. Per Git history safety, use git merge main — not rebase.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Both current failures are unrelated to this PR — which only changes src/components/Onfido/index.native.tsx. I'm not pushing a fix, since neither failure is in this PR's diff.

1. typecheck — pre-existing breakage on main.
el.ts (Greek) is missing the multipleTaxExport key that exists in the English source of truth en.ts:10538 (and es.ts:10543), so the translation type requires it and el.ts fails to compile. That key was added by a separate tax-export PR and never added to el.ts; main itself is currently red for this. It should clear once el.ts gets the missing key on main and this branch merges main in — it's not something this Onfido change can or should fix.

2. test (job 4) — flaky timeout.
tests/actions/IOUTest/SplitTest.ts › setDraftSplitTransaction › should set the associated tax for the category based on the tax expense rules failed with Exceeded timeout of 240000 ms — a slow/flaky timeout, not an assertion failure, in a tax-split test unrelated to Onfido. A re-run should pass.

The checklist check is separately still pending/red because the reviewer checklist hasn't been completed yet.

All build/lint/other test checks are green.

@garrettmknight
garrettmknight removed their request for review August 4, 2026 09:17
@situchan

situchan commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot pull main

…/App into claude-onfidoActivityIndicator

Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Merged main into claude-onfidoActivityIndicator and pushed — no conflicts. The branch is now up to date (949e8a5).

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.

2 participants