Skip to content

Return to the split overview after Remove split when editing a per diem split - #102271

Merged
NikkiWines merged 6 commits into
mainfrom
claude-perDiemRemoveSplitRevert
Oct 6, 2026
Merged

NikkiWines merged 6 commits into
mainfrom
claude-perDiemRemoveSplitRevert

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Deleting a per diem split does not delete it. It calls initSplitExpense with navigateToEditSplitExpense, which opens the split overview and then the split edit page (the menu item reads "Edit splits"). Remove split on that page edits the split draft and goes back to the overview, where Save commits the change.

initSplitExpense pushed both routes in the same tick. The overview's RHP had not mounted its inner navigator yet, so the second push replaced the overview's RHP instead of stacking on top of it. The edit page's back path is built from the screen underneath it, which was the report rather than the overview. Going back after Remove split therefore closed the RHP, the draft change was never saved, and nothing appeared to happen.

This PR opens the edit page from the overview navigation's afterTransition callback, the same pattern as dismissModalAndOpenReportInInboxTab. The edit page now stacks on top of the overview. Remove split, the header back button, and the edit page's Save all return to the overview, which shows the updated splits and the Save button. Pressing Save with one split left reverts the per diem expense to the original.

This is how the flow worked before SPLIT_EXPENSE_EDIT was migrated to dynamic routes. An earlier version of this PR reverted the split right away from the edit page. That only covered the one-split-left case, so this root-cause fix replaced it.

Fixed Issues

$ #100130
PROPOSAL: #100130 (comment)

Tests

Precondition: User has created 2 Per Diem expenses in Workspace chat

  1. Open the New Dot app
  2. Navigate to Workspace chat - Delete the report
  3. Go to selfDM and locate the per diem expense
  4. Click on one of the per diem -> More -> Split -> Save
  5. Click on one of the splits - More - Edit split - Remove split - Save
  6. Verify Split Per Diem expense reverts to the original Per Diem
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Precondition: User has created 2 Per Diem expenses in Workspace chat

  1. Open the New Dot app
  2. Navigate to Workspace chat - Delete the report
  3. Go to selfDM and locate the per diem expense
  4. Click on one of the per diem -> More -> Split -> Save
  5. Click on one of the splits - More - Edit split - Remove split - Save
  6. Verify Split Per Diem expense reverts to the original Per Diem
  • Verify that no errors appear in the JS console

AI Tests

Run locally by MelvinBot on this branch:

  • npm run typecheck: passed
  • ESLint on src/libs/actions/SplitExpenses.ts and tests/actions/IOUTest/SplitTest.ts: no new errors. Two existing no-unsafe-type-assertion errors in SplitExpenses.ts are on lines this PR doesn't touch.
  • npm run fmt (oxfmt): no formatting changes
  • npm test on tests/actions/IOUTest/SplitTest.ts and tests/unit/SplitExpenseAutoAdjustmentTest.ts: 160 of 160 passed. This includes a new test that the edit page opens only from the overview's afterTransition callback.
  • Not run: npm run spell-changed, which fails in this shallow checkout (git merge-base failed).
  • Not run: browser testing of the per diem flow. It needs a workspace with per diem rates set up, which the test account does not have.

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

Deleting a per diem split opens the split edit page instead of deleting it. Removing a split
there only edited the local draft, and nothing reverted until Save was pressed on the overview.
Now a removal that leaves one split commits the revert immediately, through a shared
useSaveSplitExpenses hook extracted from the overview's Save.

Co-authored-by: Linh Vo <linhvovan29546@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team September 25, 2026 11:34
@melvin-bot melvin-bot Bot added Melvin-Test-Android Melvin-Test-Web Triggers Melvin to run the testing steps of the PR on web labels Sep 25, 2026
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I created this PR from an upstream branch because I can't push to a fork of yours.

To take over this branch and push updates, run:

git remote add upstream https://github.com/Expensify/App.git
git fetch upstream claude-perDiemRemoveSplitRevert
git checkout -b claude-perDiemRemoveSplitRevert upstream/claude-perDiemRemoveSplitRevert
git push -u origin claude-perDiemRemoveSplitRevert

Then you can close this PR and open a new one from your fork.

@codecov

codecov Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Coverage Δ
src/libs/actions/SplitExpenses.ts 95.18% <100.00%> (+7.37%) ⬆️
... and 196 files with indirect coverage changes

@MelvinBot

Copy link
Copy Markdown
Contributor Author

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

Run details

@MelvinBot

MelvinBot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Web test steps verification results: ❌ fail

I could not test the per diem split-revert fix. Creating a per diem expense needs a per diem rate on the workspace, and on web the only way to add a rate is to upload a spreadsheet. The browser tool I use can't upload files. This PR has no written test steps yet, so I used the reproduction steps from the linked issue.

Workspace Per diem settings page showing only an Import spreadsheet option and no rates

Step Status
Precondition: a per diem expense exists (selfDM or workspace chat)Onboarding and workspace creation worked. Per diem was turned on after a simulated Control plan upgrade in the dev environment. The Per diem settings page only offers "Import spreadsheet" to add rates, with no manual "Add rate" form. I couldn't upload a file, so I couldn't add a rate or create a per diem expense.
Workspace Per diem settings page showing only an Import spreadsheet option and no rates
❌
Open the per diem expense → More → Split → Save (2 splits created)Not reached. No per diem expense could be created without a rate.
❌
Open a split → More → Edit split → Remove split reverts the expense right away to one expenseNot reached. Blocked by the same precondition.
❌
No JS console errors in the split flowNot reached. The split flow never started, and no console errors were captured.
❌

To unblock this, you can test on an account whose workspace already has per diem rates. You can also add written test steps to the PR, then retry.


view run · view recording

@linhvovan29546

Copy link
Copy Markdown
Contributor

@MelvinBot Please merge the main branch and resolve conflicts

…plitRevert

Co-authored-by: Linh Vo <linhvovan29546@users.noreply.github.com>

# Conflicts:
#	src/pages/iou/DynamicSplitExpensePage.tsx
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I merged main into this branch, resolved the conflict, and pushed the result in ee2303f.

Conflict: src/pages/iou/DynamicSplitExpensePage.tsx

  • I kept this PR's removal of quickAction from the page, because useSaveSplitExpenses now reads it.
  • I restored the useAllPersonalDetails import. main now uses personalDetails in the page for the new isSplitAction argument, but the merge had dropped the import.

Checks run locally:

  • npm run typecheck passes.
  • npm run lint on the PR files passes.
  • tests/actions/IOUTest/SplitTest.ts passes (145 of 145).

view run

style={[styles.w100, styles.mb4]}
onPress={() => {
removeSplitExpenseField(draftTransactionWithSplitExpenses, splitExpenseTransactionID, getCurrencyDecimals);
const remainingSplitExpenses = removeSplitExpenseField(draftTransactionWithSplitExpenses, splitExpenseTransactionID, getCurrencyDecimals);

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.

The function is named removeSplitExpenseField, but it returns redistributedSplitExpenses, so I don’t think the function name is appropriate here. Since function names should describe the action being performed, should we rename it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. In 003ad20, I renamed it to removeSplitExpenseAndRedistributeAmounts, which matches the existing redistributeSplitExpenseAmounts helper. I updated every reference, including both test files, and added a JSDoc noting that it returns the remaining split expenses it saved to the draft.

Typecheck, lint, and SplitTest.ts plus SplitExpenseAutoAdjustmentTest.ts (160 of 160) pass locally.


view run

…ounts

The function removes a split, redistributes amounts across the remaining splits, writes them to the split draft, and returns them, so the old name no longer described what it does. Add a JSDoc noting the return value.

Co-authored-by: Linh Vo <linhvovan29546@users.noreply.github.com>
@linhvovan29546

Copy link
Copy Markdown
Contributor

@MelvinBot One suggestion on approach: introducing useSaveSplitExpenses means refactoring the already-working DynamicSplitExpensePage.tsx (moving its Onyx reads + the updateSplitTransactionsFromSplitExpensesFlow call into a new hook) just to reuse it from the edit page. Since this is a bug fix, would it be simpler/lower-risk to leave DynamicSplitExpensePage.tsx untouched and call updateSplitTransactionsFromSplitExpensesFlow directly from DynamicSplitExpenseEditPage.tsx for the revert case instead? That keeps the diff scoped to the actual fix and avoids any chance of regressing the existing Save flow.

…plit edit page

Drop the useSaveSplitExpenses hook and restore DynamicSplitExpensePage to its main version, so the fix stays scoped to DynamicSplitExpenseEditPage.

Co-authored-by: Linh Vo <linhvovan29546@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Agreed. It's lower risk, so I made the change in c5a9a67.

  • I restored DynamicSplitExpensePage.tsx to its main version and removed the useSaveSplitExpenses hook. The existing Save flow is no longer touched.
  • DynamicSplitExpenseEditPage.tsx now calls updateSplitTransactionsFromSplitExpensesFlow directly for the revert case. It reads the same Onyx data that the overview's Save reads.
  • I kept the removeSplitExpenseAndRedistributeAmounts rename from your earlier comment.

One tradeoff: the Onyx reads for this call now exist in both pages.

Checks: typecheck, lint, oxfmt, the React Compiler check, and the split tests (160 of 160) all pass. I also updated the PR description.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The failing check is unrelated to this PR, so I didn't push a fix.

Analysis: test (job 3) fails only in tests/ui/IOURequestRedirectToStartPageTest.tsx with ReferenceError: Request is not defined. The error comes from Expo's fetch polyfill (node_modules/expo/src/winter/fetch/fetch.ts) when that test makes a network call. This PR doesn't touch that test or anything it imports. The other 3,802 tests pass, including the split tests this PR changes.

Evidence: The same two tests fail with the same error on other PRs today, for example on the fix/102492-102471-bulk-action-bar-blockers and recreate-netsuite-split-exports-toggle branches. This points to a test-environment problem that needs a fix on main.

Check independent approval also fails, but that check only clears after a reviewer approves the PR.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I rechecked the checks. This PR didn't cause the failures, so I have no code fix to push.

  • test (job 3): This is the same run as before. Two tests in tests/ui/IOURequestRedirectToStartPageTest.tsx fail with ReferenceError: Request is not defined from Expo's fetch polyfill. The same failure also appears on unrelated PRs today, including fix/102492-102471-bulk-action-bar-blockers and recreate-netsuite-split-exports-toggle. main has no fix to merge in yet. After that test is fixed on main, merge main into this branch or re-run the job.
  • Check independent approval: This check passes after a reviewer approves the PR.
  • ESLint check: This check is still pending.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I rechecked the checks. main causes both failures, not this PR, so I have no code fix to push.

After the fixes land on main, merge main into this branch or re-run the checks.

@linhvovan29546

Copy link
Copy Markdown
Contributor

@MelvinBot Quick question on the new hooks added here — isSplitPerDiemRequest/revertSplit only needs allReportActions, allReportNameValuePairs, allSnapshots, unscoped PolicyTags, unfiltered useAllPersonalDetails, etc. (lines 231-254), but since hooks can't be called conditionally, every visit to this edit page now pays for all of them — including some of the most write-heavy collections in the app — even when editing a plain non-per-diem split.

Would it make sense to move that block into a small child component, conditionally mounted only when isSplitPerDiemRequest is true, and have it report the computed split back up via a callback (e.g. onRevertReady)? That way the subscriptions only exist for the lifetime of an actual per-diem revert, and this page's common-case renders stay untouched.

There's already a precedent for this exact shape in the codebase — DeleteWorkspaceFlow, conditionally mounted from WorkspaceOverviewPage only while isDeleteWorkspaceFlowVisible, which isolates its (heavier) Onyx subscriptions from the parent list page and reports back via onDismiss/onDeleteComplete callbacks for the same reason.

The edit page mounts it only after Remove split leaves one split of a per diem expense, so its Onyx subscriptions no longer run on every visit to the edit page.

Co-authored-by: Linh Vo <linhvovan29546@users.noreply.github.com>
@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.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Yes, good call. I moved the block into a child component in 29fea25. I mount it only when a revert actually happens, not whenever isSplitPerDiemRequest is true.

  • RevertSplitExpenseFlow.tsx holds all the heavy subscriptions and renders nothing. Like DeleteWorkspaceFlow, it waits until its Onyx data has loaded, then calls updateSplitTransactionsFromSplitExpensesFlow once. That call navigates away on its own.
  • DynamicSplitExpenseEditPage.tsx now adds only the scoped violations read and a useState. Remove split stores the remaining split in that state, and the state mounts the child.

I dropped the onRevertReady callback. Mounting on the button press means the child doesn't need to hand anything back to the parent. It also means per diem edits that never remove down to one split don't pay for these subscriptions.

Checks: typecheck, lint, oxfmt, the React Compiler check, and the split tests (160 of 160) all pass. I updated the PR description. I haven't tested the per diem flow in a browser, because the test account has no per diem rates.


view run

@linhvovan29546

Copy link
Copy Markdown
Contributor

@MelvinBot I found the actual root cause of issue, and I don't think this PR fixes it.

initSplitExpense (src/libs/actions/SplitExpenses.ts) pushes the overview route and the edit route back-to-back with two synchronous Navigation.navigate() calls:

Navigation.navigate(splitExpenseOverviewRoute);
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.SPLIT_EXPENSE_EDIT.getRoute(reportID, transaction.transactionID), splitExpenseOverviewRoute));

Neither passes options, so both dispatch immediately in the same tick, before the first navigation settles. That breaks the navigation stack the edit page's "go back to overview → Save" flow depends on, so after Remove split, going back doesn't land on the overview with the updated splits and Save button — which is why it looks like nothing happened.

Fix — chain the second navigate through afterTransition (same pattern as dismissModalAndOpenReportInInboxTab.ts) so it waits for the first to start:

Navigation.navigate(splitExpenseOverviewRoute, {
    afterTransition: () => {
        Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.SPLIT_EXPENSE_EDIT.getRoute(reportID, transaction.transactionID), splitExpenseOverviewRoute));
    },
});

…erting on Remove split

initSplitExpense pushed the split overview and the split edit page in the same tick, so the edit page replaced the overview's RHP. Going back from the edit page then closed the RHP instead of returning to the overview, where Save commits the change.

Chain the edit-page navigation through afterTransition, and drop the immediate-revert workaround from the edit page.

Co-authored-by: Linh Vo <linhvovan29546@users.noreply.github.com>
@MelvinBot MelvinBot changed the title Revert a per diem split right away when removing a split leaves only one Return to the split overview after Remove split when editing a per diem split Sep 30, 2026
@MelvinBot

Copy link
Copy Markdown
Contributor Author

You're right. I confirmed the root cause in code and replaced this PR's fix with yours in d158f14.

What changed

  • SplitExpenses.ts:137-144 now opens the edit page from the overview's afterTransition callback.
  • I removed the immediate-revert workaround: RevertSplitExpenseFlow, the edit-page changes, and the removeSplitExpenseField rename. Only the new return value needed that rename.
  • The PR diff is now SplitExpenses.ts plus one unit test showing the edit page waits for afterTransition. I updated the title and description to match.

Why the old flow broke: the first push adds an RHP with no inner state yet. Because of that, the second push doesn't nest inside it. RootStackRouter treats it as a modal opened from a modal and removes the overview's RHP (RootStackRouter.ts:175-184). The edit page's backTo then resolves to the report, not the overview. As a result, Remove split closes the RHP without saving.

History and open points
  • The second navigate call came in with migrate SPLIT_EXPENSE_EDIT.
  • With the fix, the user still has to press Save on the overview to revert the expense. That matches how the flow worked before the migration.
  • Not verified at runtime: afterTransition waits for the next transition of any kind. Something like the More menu closing could fire it before the overview's RHP has mounted.
    • I couldn't test this in a browser, because creating a per diem expense on web needs a spreadsheet upload.
    • Please check it when you test: after Delete → Remove split, you should land on the overview with the Save button.

Checks: typecheck, formatting, and the split tests (160 of 160) pass. Lint shows no new errors.


view run

@linhvovan29546

Copy link
Copy Markdown
Contributor

@MelvinBot Please add the test step and QA step

Preconditions: User has created 2 Per Diem expenses in Workspace chat

Open the New Dot app
Navigate to Workspace chat - Delete the report
Go to selfDM and locate the per diem expense
Click on one of the per diem -> More -> Split -> Save
Click on one of the splits - More - Edit split - Remove split - Save
Verify Split Per Diem expense reverts to the original Per Diem

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done. I added your steps to both the Tests and QA Steps sections of the PR description, with the precondition and numbered steps. I kept the existing JS console check under each section and left the Offline tests section unchanged.


view run

@linhvovan29546

Copy link
Copy Markdown
Contributor

@MelvinBot Please mark the PR ready for review

@MelvinBot
MelvinBot marked this pull request as ready for review September 30, 2026 09:40
@MelvinBot
MelvinBot requested a review from a team as a code owner September 30, 2026 09:40
@MelvinBot

Copy link
Copy Markdown
Contributor Author

This PR is now ready for review.

The Tests and QA Steps sections in the description are still TODO placeholders. The human co-author needs to fill them in.


view run

@melvin-bot

melvin-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown

@linhvovan29546 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 removed request for a team September 30, 2026 09:40
@linhvovan29546

linhvovan29546 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Screen.Recording.2026-09-30.at.16.45.39.mov
Android: mWeb Chrome
Screen.Recording.2026-09-30.at.16.42.56.mov
iOS: HybridApp
Screen.Recording.2026-09-30.at.16.47.39.mov
iOS: mWeb Safari
Screen.Recording.2026-09-30.at.16.41.28.mov
MacOS: Chrome / Safari
Screen.Recording.2026-09-30.at.16.39.41.mov

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d158f14aeb

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

// so going back from it (e.g. after Remove split) closes the RHP instead of returning to the overview to Save.
Navigation.navigate(splitExpenseOverviewRoute, {
afterTransition: () => {
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.SPLIT_EXPENSE_EDIT.getRoute(reportID, transaction.transactionID), splitExpenseOverviewRoute));

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 stale editor navigation after leaving the overview

If the user presses Back or navigates to another destination while the overview is still transitioning in, this callback remains queued and later opens the captured edit route over the destination they chose. Navigation.navigate schedules afterTransition through TransitionTracker, which waits for transitions to finish but does not cancel callbacks when the overview is popped; guard or cancel this continuation when its overview is no longer active.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@MelvinBot Please check this feedback

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.

The race is real, but I don't think it's worth changing here. I recommend no code change.

  • Impact is small. The edit page only reopens if the user leaves the overview while its open animation is still running, which is a few hundred milliseconds right after they tap the menu item. Even then, the edit page just opens where they didn't expect it. No data is saved or lost.
  • This matches the existing pattern. dismissModalWithReport chains a navigate through an uncancelled afterTransition the same way.
  • A guard adds more risk than it removes. navigate doesn't return the cancel handle, so the only option is an active-route check inside the callback. isActiveRoute strips query params from the active route but not from the route you pass in. The overview route is dynamic and can carry query params, for example from Search. A mismatch there would block the edit page every time and break the main flow. I can't test that flow in the browser, because the test account has no per diem rates.
How the timing works

navigate passes afterTransition to TransitionTracker.runAfterTransitions with waitForUpcomingTransition: true. runAfterTransitions waits for the next transition to start, then until no transitions are active. If the user goes Back during the overview's push, the pop is just another transition. The callback then fires after the pop ends. Each wait is capped at 1000 ms (MAX_TRANSITION_START_WAIT_MS, MAX_TRANSITION_DURATION_MS).

If you'd still like a guard, reply and I'll add one. It would compare paths without query params.


view run

@melvin-bot
melvin-bot Bot requested a review from NikkiWines September 30, 2026 09:50

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

:shipit: sorry for the delay here

@NikkiWines
NikkiWines merged commit 3bf0738 into main Oct 6, 2026
50 of 56 checks passed
@NikkiWines
NikkiWines deleted the claude-perDiemRemoveSplitRevert branch October 6, 2026 14:41
@OSBotify

OSBotify commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🚧 NikkiWines has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify OSBotify mentioned this pull request Oct 6, 2026
100 tasks done
@OSBotify

OSBotify commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/NikkiWines in version: 9.5.4-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No help site update is needed. This PR fixes a navigation bug so that Remove split on a per diem split returns you to the split overview and Save commits the change, which is what users already expect.


view run

@OSBotify

OSBotify commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/puneetlath in version: 9.5.4-3 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants