Repository navigation
Bind category and tag dynamic routes to the screen that builds them - #100501
Conversation
WorkspaceCategoriesPage, WorkspaceTagsPage, CategorySettingsPage and DynamicTagSettingsPage all built their navigation targets at press time with a bare createDynamicRoute, which resolves the suffix against whatever route is active at that instant. A second press landing before the first navigation settles reads the route the first press just opened as its base, so the suffix is appended twice and the doubled path fails entryScreens validation and falls through to the not-found wildcard. Use useScreenBoundDynamicRoute on all four pages so the base is latched to the screen that owns the controls. Both presses then build the same path, and an identical destination is already dropped before dispatch. The hook latched a bare "/" when its focus effect ran before a deep link had resolved, which stripped the workspace prefix from every route built from it. Treat that the same as the empty route the guard already rejected.
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
dylanexpensify
left a comment
There was a problem hiding this comment.
Looks good from a product POV
|
2026-09-08.08-08-52.mp4 |
The category and tag settings pages are pushed dynamic screens, so neither the active route nor a focus-latched base holds once the first press navigates away. Compose each page's own route from the params it already has and use that as the base, so a second press produces the same path as the first.
054bbd1
Company address and Plan type built their targets from the active route at press time, so a second press appended the suffix onto the first destination and landed on the not-found page.
roryabraham
left a comment
There was a problem hiding this comment.
LGTM, though the distinction between buildDynamicRoute and createDynamicRoute isn't immediately clear from the names. We should consider a separate PR to rename them with more descriptive names.
@mukhrr Can you explain what caused this bug? |
@dmkt9 yes, I used App/src/libs/Navigation/helpers/getStateFromPath.ts Lines 45 to 57 in 27a8ba4 |
@mukhrr Thanks. So |
|
hmm what you say makes sense. let me check if we don't regress anything |
A screen pushed into the RHP focuses while the navigation state is still mid-transition, where getActiveRoute reports a dynamic screen as its suffix alone, so the latched base dropped the workspace prefix. With the matched path first, the category and tag settings pages take buildDynamicRoute back.
|
@dmkt9 mind checking once more pls? thanks |
|
@mukhrr Conflicts |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ffac8fe671
ℹ️ 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".
# Conflicts: # src/pages/workspace/categories/CategorySettingsPage.tsx
|
@MelvinBot review the changes and identify all potential bugs |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ 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". |
Review: potential bugsThree things in the hook change look wrong to me. The page conversions themselves are mechanical and read fine (all 1. The latched base goes stale when a settings page rewrites its own URL — likely reintroduces the Not Found you reportedBoth settings pages this PR converts rewrite their own URL in place after a rename, with no blur/focus cycle:
Repro to run: open a category's settings → rename it → without leaving the page, tap Whether this actually bites depends on React Compiler's dependency granularity for the un-memoized Why the effect may or may not re-run
2.
|
i don't think these are valid bugs. i checked cc @dmkt9 |
@dmkt9 can you tell me more about this? i mean I am not able to trigger that early-out. |
# Conflicts: # src/pages/workspace/categories/CategorySettingsPage.tsx # src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@mukhrr I'm not sure what you tested. You can add some
I know that. But let's make it more robust if we can |
A screen that focuses before the container is ready, or before the root state carries its navigator, never got a base, and a rename changed the route in place without a re-latch. Listen for state events for the whole focus session and latch whenever the route is in the root state.
|
@dmkt9 done |
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ 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". |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safarichrome.mp4 |
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #100436 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 roryabraham has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/roryabraham in version: 9.4.81-0 🚀
|
|
🤖 No help site changes are required for this PR. I did not create a docs PR. Why: Every change here is internal navigation plumbing. The PR swaps What I checkedFiles changed (6 source files, 2 test files):
In the four page files, every diff line is the same mechanical swap: Criteria that would have triggered a docs update, and what I found:
Help site scan: I searched The only user-visible difference is that a fast double tap on a category or tag row no longer lands on the not-found page. That is a bug fix restoring documented behavior, not new behavior to document. Since no help site changes are needed, there is no linked docs PR to review or mark |
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.4.81-1 🚀
|
1 similar comment
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.4.81-1 🚀
|
Explanation of Change
The category and tag pages built their navigation targets at press time with a bare
createDynamicRoute, which resolves the suffix against whatever route is active at that instant. A second press landing before the first navigation settles reads the route the first press just opened as its base and appends the suffix again, and the doubled path falls through to the not-found wildcard. Three sibling screens shared the pattern, so all four now useuseScreenBoundDynamicRoute, which latches the base to the screen that owns the controls.That hook needed one fix: its focus effect ignored an empty active route on cold start but not a bare
/, which stripped the workspace prefix from every route built from it.Also fixed all 5 cases found here
Fixed Issues
$ #100436
PROPOSAL: #100436 (comment)
Tests
Precondition: an admin on a workspace with Categories and Tags enabled, at least one tag, and a narrow window (the second press only reaches the row at mWeb width). The two taps must land within about 80ms of each other.
Offline tests
None of these routes make a request, so the behavior is identical offline. Repeat steps 1 to 9 with the network turned off and verify each page still opens and the not-found page does not appear.
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_app.mp4
Android: mWeb Chrome
android-web.mp4
iOS: Native
ios.app.mp4
iOS: mWeb Safari
IOS_mweb.mp4
MacOS: Chrome / Safari
web.mp4