Repository navigation
[CP Staging] fix(navigation): stop goUp at a pop prevented by a beforeRemove guard - #102022
Conversation
|
@codex review |
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.
|
|
@MelvinBot review this |
ReviewThe approach is right — bailing out of 1. After the user confirms "Discard", the rest of the back navigation is dropped
Before this PR, For the single-pop flow in Web - Expense - Back button does not trigger discard change modal on Description this is fine. Can you confirm the multi-pop case still lands on the requested route, or note it as a known follow-up? 2.
|
| dispatch(popToNavigator); | ||
| // A prevented pop hands control to its guard: going on would change screens under its prompt or re-send the pop, | ||
| // which closes the prompt. On confirm the guard replays only this pop, which usually is the whole way back. | ||
| if (!dispatch(popToNavigator)) { |
There was a problem hiding this comment.
Can there be any cases in the app where we should continue after a no-op?
There was a problem hiding this comment.
afaik not today: goUp's pops are all pop(n > 0) computed from the current state, so the only noop they can produce is a beforeRemove guard preventing them, and the dispatches after the loop are goUp's last step anyway
| } | ||
| navigationRef.current?.dispatch(actionToDispatch); | ||
| let wasApplied = true; | ||
| const unsubscribe = navigationContainer.addListener('__unsafe_action__', (event) => { |
There was a problem hiding this comment.
unsafe_action is synchronous. Are you sure that the navigation events will happen before the listener is triggered, but after it's registered?
There was a problem hiding this comment.
It's the order: dispatch -> onAction -> beforeRemove -> emit('__unsafe_action__') all run synchronously inside the dispatch() call, so the event always should land between addListener and unsubscribe()
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-09-23.at.22.49.02.movAndroid: mWeb ChromeScreen.Recording.2026-09-23.at.22.50.28.moviOS: HybridAppScreen.Recording.2026-09-23.at.22.51.52.moviOS: mWeb SafariScreen.Recording.2026-09-23.at.22.52.21.movMacOS: Chrome / SafariScreen.Recording.2026-09-23.at.22.47.09.mov |
lakchote
left a comment
There was a problem hiding this comment.
LGTM. Thoughts @Julesssss?
| expect(skipSpy).toHaveBeenCalledTimes(1); | ||
| expect(cancelSkipSpy).toHaveBeenCalledTimes(1); |
There was a problem hiding this comment.
It would be better to test the outcome of cancelSkipSpy being called, this would still pass if it's behaviour changed.
…-pop-on-prevented-remove [CP Staging] fix(navigation): stop goUp at a pop prevented by a beforeRemove guard (cherry picked from commit 6dd93ed) (cherry-picked to staging by lakchote)
|
🚧 Julesssss has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🔁 Filed a Staging retest request for deploy blockers #102010 after this PR was cherry-picked to staging. |
|
🚀 Cherry-picked to staging by https://github.com/lakchote in version: 9.4.92-5 🚀
|
Help site review: no changes requiredNo draft PR created — nothing under Why: this is an internal navigation fix. It changes how What I checked:
If you'd rather the discard-changes prompt be documented as behavior in its own right, that's a net-new article request rather than a fix follow-up — say the word and I'll draft it.
@WojtekBoman, since no help site changes are needed, there's no linked docs PR to review — please confirm you agree with that conclusion, or point me at an article you think should cover this and I'll open the draft PR. |
|
Requested QA retest here. |
|
🚀 Deployed to production by https://github.com/lakchote in version: 9.4.92-8 🚀
Bundle Size Analysis (Sentry): |
|
🚀 Cherry-picked to staging by https://github.com/lakchote in version: 9.4.93-0 🚀
|
No help site changes requiredThis PR is an internal navigation fix, not a product behavior change. No files under Why:
@WojtekBoman, please confirm you agree that no help site update is needed. If you think an article should cover this behavior, tell me which one and I'll draft the docs PR. |
|
🚀 Deployed to production by https://github.com/lakchote in version: 9.4.93-3 🚀
Bundle Size Analysis (Sentry): |
cc: @huult @KJ21-ENG
Explanation of Change
goUpkept sending pops after abeforeRemoveguard prevented one, so the discard-changes guard saw a second pop and closed its own prompt.dispatchnow detects a dropped action via thenoop__unsafe_action__event, stopsgoUpthere, and disarms the focus-restore skip. Tests added inGoBackTests.tsx.Fixed Issues
$ #102010
PROPOSAL:
Tests
Test 1 - Discard prompt shows on RHP Back (Description)
Test 2 - Discard prompt shows on RHP Back (Merchant) and Discard leaves the page
Test 3 - Save still goes back normally
Offline tests
N/A - navigation-only change, no API calls or Onyx writes are affected.
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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.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