Fix: Not here page after removing split when sibling is on submitted report#90961
Fix: Not here page after removing split when sibling is on submitted report#90961MelvinBot wants to merge 8 commits into
Conversation
…eport When a split expense has one child moved to a submitted report, removing the remaining child via Edit Split leaves the expense report empty but the navigation guard is not armed, causing a "Not here" page. This broadens the empty-report detection to cover the moved-child scenario alongside the existing reverse-split case. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
|
Fixed the failing ESLint check: replaced the forbidden non-null assertion ( |
|
|
||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
@MelvinBot Could you please explain why this change is needed?
|
The bug occurs in a specific split-expense scenario:
Why the existing code fails: The function What the PR changes:
|
|
@MelvinBot When I delete an approved expense or remove an approved expense from an edit split, the server returns an error. If I open any of the split expense and try to edit the split, a "Not here" page appears instead of the edit split screen. Could you please investigate? |
|
Good catch — this is a pre-existing bug (not introduced by this PR) with two distinct root causes: 1. 2. Why this happens with approved expenses specificallyThere is no client-side validation preventing removal of children on approved/submitted reports from splits. The The navigation is also irreversible on failure — This issue exists on |
There was a problem hiding this comment.
Screenshots/Videos
Android: HybridApp
Screen.Recording.2026-05-20.at.15.35.00.mov
Android: mWeb Chrome
Screen.Recording.2026-05-20.at.15.39.51.mov
iOS: HybridApp
Screen.Recording.2026-05-20.at.15.21.35.mov
iOS: mWeb Safari
Screen.Recording.2026-05-20.at.15.24.51.mov
MacOS: Chrome / Safari
Screen.Recording.2026-05-20.at.17.53.47.mov
Screen.Recording.2026-05-20.at.17.54.49.mov
|
@MelvinBot Please add the following in the QA steps and tests and complete the Author checklist.
|
|
Updated the PR body with the provided QA steps and tests, and completed the applicable Author Checklist items. Items left unchecked are conditional (new files, CSS, assets, UI changes, etc.) and don't apply to this PR, or require manual platform testing (screenshots, multi-platform runs). |
|
@MelvinBot Please check all the items in Author Checklist. All the items need to be checked to pass the PR Author Checklist tests. |
|
I can't check all the Author Checklist items because most of them are human attestations — they confirm that you personally tested on all platforms, verified no console errors, included screenshots, etc. Checking them without actually doing those things would be dishonest and would undermine the review process. The PR body also still has TODO placeholders for Tests, Offline tests, and QA Steps that need to be filled in by the human co-author. What you should do: Fill in the test steps, add screenshots/videos, and then check off each item as you complete it. Once that's done, the PR Author Checklist check will pass. |
|
@MelvinBot I have reviewed the author checklist and confirm that all items are complete. Please tick all the items in the checklist. |
|
All items in the PR Author Checklist have been ticked as requested by |
|
@MelvinBot Please mark this PR "ready for review". |
|
Done — PR is now marked as ready for review. |
|
@sobitneupane 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] |
sobitneupane
left a comment
There was a problem hiding this comment.
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 Issuessection 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
Testssection - I verified the steps for Staging and/or Production testing are in the
QA stepssection - 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 verified the steps for local testing are in the
- 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.
toggleReportand notonIconClick). - I verified that comments were added to code that is not self explanatory
- I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
- I verified any copy / text shown in the product is localized by adding it to
src/languages/*files and using the translation method - I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
- I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
- I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
- I verified the JSDocs style guidelines (in
STYLE.md) were followed
- 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.
- 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 usingAvatarhave been tested & I retested again) - I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
- I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
- 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
thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor) - Any internal methods bound to
thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick) - 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
Avataris modified, I verified thatAvataris 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
Designlabel and/or tagged@Expensify/designso the design team can review the changes.
- If a new page is added, I verified it's using the
ScrollViewcomponent to make it scrollable when more elements are added to the page. - 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
mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps. - I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #88213 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95d16ddd78
ℹ️ 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 willExpenseReportBeEmpty = | ||
| !isLastTransactionInReport && | ||
| !!expenseReportID && | ||
| transactionsOnExpenseReport.length > 0 && | ||
| transactionsOnExpenseReport.every((tx) => !!tx?.transactionID && removedChildTransactionIDs.has(tx.transactionID)); | ||
| const willDeleteExpenseReport = isLastTransactionInReport || willExpenseReportBeEmpty; |
There was a problem hiding this comment.
Avoid treating replace-all split edits as report deletion
The new willExpenseReportBeEmpty heuristic assumes the report is deleted whenever every current transaction ID is absent from splitExpenses, but that also happens when a user replaces all existing split rows with newly added rows (new splits do not yet have existing transaction IDs). In that edit path, updateSplitTransactions can recreate transactions in the same expense report, so the report is not actually deleted; however this condition sets the delete back URL and takes the navigateBackOnDeleteTransaction branch, which navigates away from a still-valid report and removes the thread screen unnecessarily.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The concern is not valid — willExpenseReportBeEmpty does not false-positive in the replace-all scenario.
Why: If all original splits were replaced with new rand64() IDs, then removedChildTransactionIDs would contain all original child transaction IDs (correctly — those transactions are being removed). The .every() would return true, which is the correct result: the expense report will be emptied of its current transactions.
The concern seems to assume removedChildTransactionIDs could be empty in this case, causing vacuous truth on .every(). But originalChildTransactions comes from Onyx via getChildTransactions() — it reflects persisted state, not the UI draft. So if originals exist, they'll be in the set.
Additionally, even if originalChildTransactions were somehow empty (making removedChildTransactionIDs empty), .every() on a non-empty transactionsOnExpenseReport array (guarded by .length > 0) would return false — because removedChildTransactionIDs.has(tx.transactionID) fails for every element. No false positive.
There was a problem hiding this comment.
@MelvinBot The concern is valid. I tested it and can confirm that if we remove all existing splits and then add new ones using “Add Split” and save, it navigates away from a still-valid report and unnecessarily removes the thread screen.
There was a problem hiding this comment.
You're right — I was wrong to dismiss the concern. When all existing splits are removed and new ones are added via "Add Split", willExpenseReportBeEmpty false-positives because removedChildTransactionIDs contains all original IDs while the new splits (with fresh rand64() IDs) aren't accounted for.
Fixed by adding a hasNewSplitsBeingAdded guard — if any split expense has a transactionID not present in the original children, new transactions will be created on the report, so we skip the empty-report navigation path.
|
@MelvinBot Could you please review this comment? |
When the user removes all existing splits but adds new ones via "Add Split", those new transactions will be created on the same expense report, so the report will not actually be empty. Add a hasNewSplitsBeingAdded guard to prevent false-positive navigation. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
|
@sobitneupane 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 I’m not sure why this complexity is needed. Can’t we just rely on If at least one split expense has a Am I missing any edge cases here? |
|
That's a reasonable intuition, but the simpler check would give the wrong answer in the exact scenario this PR fixes.
The simplified check ("any splitExpense.reportID matches expenseReportID → report not empty") would say the report isn't empty. But that's wrong — child A is the one being reverted back to the original unsplit transaction during processing. The expense report ends up with zero transactions, causing the "Not here" page. The That said, if you see a cleaner way to express this (e.g., a helper function), happy to refactor — the important thing is that we compare against persisted transactions on the report, not the draft split expenses. |
sobitneupane
left a comment
There was a problem hiding this comment.
Updated the screen recordings: #90961 (review)
| if (!params.transactionReport?.parentReportID) { | ||
| Navigation.navigateBackToLastSuperWideRHPScreen(); | ||
|
|
||
| requestAnimationFrame(() => { |
There was a problem hiding this comment.
❌ CONSISTENCY-3 (docs)
The requestAnimationFrame block that guards on transactionThreadReportScreen?.key and calls Navigation.removeScreenByKey is now duplicated three times in this function (lines ~1418, ~1438, and here at ~1451). Each instance performs the exact same cleanup logic.
Extract the repeated pattern into a local helper to eliminate the duplication:
const removeTransactionThreadReportScreen = () => {
requestAnimationFrame(() => {
if (!transactionThreadReportScreen?.key) {
return;
}
Navigation.removeScreenByKey(transactionThreadReportScreen.key);
});
};Then replace all three occurrences with a call to removeTransactionThreadReportScreen().
Reviewed at: 5f7e390 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Done — extracted removeTransactionThreadReportScreen helper and replaced all four occurrences.
Extract the repeated requestAnimationFrame + removeScreenByKey pattern into a removeTransactionThreadReportScreen helper, replacing all four identical occurrences in updateSplitTransactionsFromSplitExpensesFlow. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
| !!expenseReportID && | ||
| transactionsOnExpenseReport.length > 0 && | ||
| transactionsOnExpenseReport.every((tx) => !!tx?.transactionID && removedChildTransactionIDs.has(tx.transactionID)); | ||
| const willDeleteExpenseReport = isLastTransactionInReport || willExpenseReportBeEmpty; |
There was a problem hiding this comment.
Can you help me understand the semantic meaning of isLastTransactionInReport, and how we're using it here? I think we might need to rename it. Just from its definition, it seems like it means "this is the last transaction on the report and it's a reverse split, so it will be removed", right?
Then, we define willExpenseReportBeEmpty with !isLastTransactionInReport && ...
Then, on this line we do willDeleteExpenseReport = isLastTransactionInReport || willExpenseReportBeEmpty
I think those last two conditions are needlessly complex, right? If isLastTransactionInReport is true, willExpenseReportBeEmpty will be false, but willDeleteExpenseReport will be true because of the OR. I believe you that this works, but can we simplify it in a way that makes more sense to read?
There was a problem hiding this comment.
Good news — the latest commit (cea7307) already addressed the complexity concern. The willExpenseReportBeEmpty logic was simplified per sobitneupane's suggestion, so the !isLastTransactionInReport guard and all the removedChildTransactionIDs / originalChildTransactionIDs / hasNewSplitsBeingAdded machinery is gone. The current code at SplitTransactionUpdate.ts:1370-1373 is now:
const isLastTransactionInReport = isReverseSplitOperation && transactionsOnExpenseReport.length === 1;
const willExpenseReportBeEmpty = !!expenseReportID && !splitExpenses.some((expense) => expense?.reportID === expenseReportID);
const willDeleteExpenseReport = isLastTransactionInReport || willExpenseReportBeEmpty;Regarding the naming: you're right that isLastTransactionInReport is misleading — it doesn't just mean "last transaction on the report", it means "this is a reverse-split operation and the transaction being reversed is the only one on the report." A name like isReverseSplitRemovingLastTransaction would be more accurate. sobitneupane — would you like me to rename it?
|
@MelvinBot The case you mentioned here will be caught by My suggestion is to keep existing logic for What do you think? |
|
You're right on both counts, and your suggestion is a cleaner approach. Let me walk through why:
I'll implement your suggestion — it achieves the same correctness with much less complexity. Lines 1375–1390 can be replaced with: const willExpenseReportBeEmpty = !splitExpenses.some((expense) => expense?.reportID === expenseReportID);
const willDeleteExpenseReport = isLastTransactionInReport || willExpenseReportBeEmpty; |
…ectly Replace the complex removedChildTransactionIDs / originalChildTransactionIDs / hasNewSplitsBeingAdded logic with a simpler check: if no split expense targets the current expenseReportID, the report will be empty. This works because new splits created via "Add Split" inherit reportID from the draft transaction. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
|
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. |
|
@MelvinBot Jest Unit Tests / test (job 4) and React compiler compliance check is failing. |
The simplified willExpenseReportBeEmpty check relies on splitExpenses having reportID set, which matches production behavior (all split expenses inherit reportID from the draft transaction). Update test fixtures to include reportID for realistic test data. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
|
Fixed both failing checks: Jest test (job 4): The test React Compiler Compliance: This was a 401 "Bad credentials" error calling the GitHub API — an infrastructure/auth issue unrelated to this PR. Should resolve on re-run. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7ad5c8eae
ℹ️ 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 willExpenseReportBeEmpty = !!expenseReportID && !splitExpenses.some((expense) => expense?.reportID === expenseReportID); | ||
| const willDeleteExpenseReport = isLastTransactionInReport || willExpenseReportBeEmpty; |
There was a problem hiding this comment.
Recompute report-deletion check against all report transactions
willExpenseReportBeEmpty is derived only from splitExpenses, so it becomes true whenever no remaining split child points at expenseReportID, even if that expense report still contains other non-split transactions. In that case this path incorrectly treats the report as deleted and runs navigateBackOnDeleteTransaction/setDeleteTransactionNavigateBackUrl, causing an unnecessary back navigation away from a still-existing report. The deletion check should also account for the full set of transactions on the report (e.g., transactionsOnExpenseReport) before deciding to use delete-report navigation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@MelvinBot Could you please review the suggestoins?
There was a problem hiding this comment.
@MelvinBot Could you please review the suggestion?
There was a problem hiding this comment.
Valid point. The splitExpenses-only check would false-positive if the expense report has non-split transactions alongside split children — it would conclude the report is empty when those other transactions still exist.
Fixed in 67064ac by adding a reportHasOnlySplitChildren guard that verifies every transaction on the report is a child of this split before concluding it will be empty:
const originalChildIDs = new Set(originalChildTransactions.map((tx) => tx?.transactionID).filter(Boolean));
const reportHasOnlySplitChildren = transactionsOnExpenseReport.every((tx) => !!tx?.transactionID && originalChildIDs.has(tx.transactionID));
const willExpenseReportBeEmpty = !!expenseReportID && reportHasOnlySplitChildren && !splitExpenses.some((expense) => expense?.reportID === expenseReportID);All 91 split tests pass.
The simplified splitExpenses-only check could false-positive when the expense report contains non-split transactions alongside split children. Add a guard to verify all transactions on the report are split children before concluding the report will be empty. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67064acaaa
ℹ️ 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 reportHasOnlySplitChildren = transactionsOnExpenseReport.every((tx) => !!tx?.transactionID && originalChildIDs.has(tx.transactionID)); | ||
| const willExpenseReportBeEmpty = !!expenseReportID && reportHasOnlySplitChildren && !splitExpenses.some((expense) => expense?.reportID === expenseReportID); |
There was a problem hiding this comment.
Guard empty report transaction set before delete-route fallback
willExpenseReportBeEmpty can evaluate to true when transactionsOnExpenseReport is empty, because every(...) is vacuously true. In that case (for example, when allTransactionsList is partial/stale in this flow), the code will set delete-navigation state and take the delete-report back path even though the expense report may still exist, causing incorrect navigation away from the current split edit flow. Add an explicit transactionsOnExpenseReport.length > 0 guard before treating the report as “only split children.”
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@MelvinBot Could you please review the suggestion?
There was a problem hiding this comment.
Valid catch — every() on an empty array is vacuously true. Fixed in 10266e5 by adding transactionsOnExpenseReport.length > 0 to reportHasOnlySplitChildren.
… is empty Add length > 0 check to reportHasOnlySplitChildren so that an empty or partial allTransactionsList does not cause willExpenseReportBeEmpty to false-positive and incorrectly trigger delete-report navigation. Co-authored-by: Sobit Neupane <sobitneupane@users.noreply.github.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
|
@chuckdries Could you please review it again when you get a chance? |
Explanation of Change
When a split expense has one child moved to another report and that report is submitted, performing "Edit Split > Remove split > Save" on the remaining child causes a "Not here" page. This happens because the
isReverseSplitOperationflag isfalse(the remaining split is on a submitted report, sohasEditableSplitExpensesLeftisfalse), which meanssetDeleteTransactionNavigateBackUrlis never called — even though the update still optimistically deletes the last transaction from the expense report.This PR broadens the empty-report detection in
updateSplitTransactionsFromSplitExpensesFlowby computing which original child transactions will be removed and checking whether that leaves the expense report empty. When it does, the fallback navigation URL is set and thenavigateBackOnDeleteTransactionpath is used, regardless of whether it's a reverse split. The fix also reorders the post-save navigation checks so the delete-report path takes priority over the generic!parentReportIDfallback.Fixed Issues
$ #88213
PROPOSAL: #88213 (comment)
Tests
Precondition: Create a manual expense in a Workspace chat.
Offline tests
N/A - This fix addresses navigation logic that depends on server-side report state (submitted reports). The core behavior requires online connectivity to submit reports and move splits.
QA Steps
Precondition: Create a manual expense in a Workspace chat.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari