Skip to content

Support GL codes for dependent multi-level tags - #100885

Merged
blimpich merged 12 commits into
Expensify:mainfrom
neerajbachani:fix/98488-dependent-tags-gl-codes
Sep 25, 2026
Merged

blimpich merged 12 commits into
Expensify:mainfrom
neerajbachani:fix/98488-dependent-tags-gl-codes

Conversation

@neerajbachani

@neerajbachani neerajbachani commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Dependent multi-level tags can have GL codes at each level (same as independent tags), but the workspace tag settings flow could not resolve or update them correctly when duplicate display names exist under different parents.

This PR:

  • Adds findPolicyTagEntryByParentFilter to resolve the correct tag record (and Onyx storage key) using parentTagsFilter
  • Sends parentTagsFilter with UpdatePolicyTagGLCode so the backend updates the correct dependent tag
  • Unblocks the GL code row in dependent tag settings (previously hidden when hasDependentTags was true)
  • Passes parentTagsFilter through tag list → settings → GL code navigation
  • Fixes post-save navigation for level 1+ tags (Region/City) by popping the stack on back instead of rebuilding the route, which was dropping parentTagsFilter and showing NotFound even though the GL code saved successfully

The “Show GL codes when selecting a tag” toggle in Tags → More → Settings remains visible and unchanged — dependent tags are expected to support GL codes when that setting is enabled.

Fixed Issues

$ #98488
PROPOSAL:

Tests

  1. Open a Control workspace with multi-level dependent tags imported using a CSV like State / Region / City with adjacent GL columns or simply use this file
    multi-level-dependent-tags-with-gl-codes.csv
    (see QA import settings below)
  2. Go to Workspace → Tags → More → Settings and confirm “Show GL codes when selecting a tag” is visible and can be toggled
  3. Open the State tag list, select a tag (e.g. California), open GL code, edit the value, and tap Save
  4. Confirm you return to tag settings (no NotFound page) and the updated GL code is shown
  5. Open the Region tag list, select a tag (e.g. South), open GL code, tap Save without editing
  6. Confirm you return to tag settings (no NotFound page) and the GL code is unchanged
  7. Open the City tag list, select a tag (e.g. Austin), open GL code, edit the value, and tap Save
  8. Confirm you return to tag settings (no NotFound page) and the updated GL code is shown
  9. Repeat steps 3–8 on web and one native platform
  • Verify that no errors appear in the JS console

Offline tests

  1. Go offline
  2. Open a dependent tag at Region or City level → GL code → edit and tap Save
  3. Confirm optimistic UI updates the GL code and navigation returns to tag settings without NotFound
  4. Go back online and confirm the GL code persists after refresh

QA Steps

Import settings:

  • First row is the title for each tag list → ON
  • These are independent tags → OFF (dependent)
  • There is a GL code in the adjacent column → ON

Use a dependent multi-level CSV with GL columns (e.g. State / State GL / Region / Region GL / City / City GL) or simply use this file
multi-level-dependent-tags-with-gl-codes.csv

  1. Import the CSV into a Control workspace
  2. Go to Workspace → Tags → More → Settings
  3. Expected: “Show GL codes when selecting a tag” toggle is visible
  4. Enable Show GL codes when selecting a tag if it is off
  5. Open the State list → select a tag → GL code → edit → Save
  6. Confirm return to settings with updated GL code (no NotFound)
  7. Open the Region list → select a tag → GL code → Save without editing
  8. Confirm return to settings with no NotFound
  9. Open the City list → select a tag → GL code → edit → Save
  10. Confirm return to settings with updated GL code (no NotFound)
  11. Repeat on all platforms
  • 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 it as 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
Screen.Recording.2026-09-11.at.2.39.40.PM.online-video-cutter.com.mp4
Android: mWeb Chrome
Record_2026-09-11-15-30-27.online-video-cutter.com.mp4
iOS: Native
Screen.Recording.2026-09-11.at.2.16.49.PM.online-video-cutter.com.mp4
iOS: mWeb Safari
Screen.Recording.2026-09-11.at.2.19.45.PM.online-video-cutter.com.mp4
MacOS: Chrome / Safari
Screen.Recording.2026-09-11.at.1.43.47.AM.online-video-cutter.com.mp4

Send parentTagsFilter when updating tag GL codes so duplicate tag names resolve correctly, and fix post-save navigation for level 1+ tags by popping the stack instead of rebuilding the route.

$ Expensify#98488
@codecov

codecov Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

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.

Files with missing lines Coverage Δ
src/ROUTES.ts 30.68% <ø> (+1.29%) ⬆️
src/libs/PolicyUtils.ts 83.13% <90.90%> (+1.30%) ⬆️
src/libs/actions/Policy/Tag.ts 68.16% <50.00%> (-0.03%) ⬇️
...es/workspace/tags/DynamicWorkspaceViewTagsPage.tsx 0.00% <0.00%> (ø)
...lpers/dynamicRoutesUtils/appendParentTagsFilter.ts 0.00% <0.00%> (ø)
src/pages/workspace/tags/DynamicTagGLCodePage.tsx 0.00% <0.00%> (ø)
...rc/pages/workspace/tags/DynamicTagSettingsPage.tsx 0.00% <0.00%> (ø)
... and 977 files with indirect coverage changes

Add eslint-disable comments for Onyx record keys Roadshow-1 and GL Code in the dependent tag GL code test.
Build Roadshow-1 and GL Code via bracket assignment instead of eslint-disable comments, matching other SetPolicyTagGLCode tests in PolicyTagTest.
Type engineeringRoadshowTag as PolicyTag so bracket assignment to GL Code passes TypeScript.
@neerajbachani
neerajbachani marked this pull request as ready for review September 10, 2026 22:09
@neerajbachani
neerajbachani requested review from a team as code owners September 10, 2026 22:09
@melvin-bot
melvin-bot Bot requested review from DylanDylann and dylanexpensify and removed request for a team September 10, 2026 22:09
@melvin-bot

melvin-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

@DylanDylann 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 requested review from a team and removed request for a team September 10, 2026 22:09

@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: 67b2d4d5fe

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

Addresses CONSISTENCY-3 review feedback by deduplicating parentTagsFilter query param construction.
@neerajbachani

Copy link
Copy Markdown
Contributor Author

Working on the checklist and recordings for all platforms. The failing tests appear to be unrelated to this PR; will try re-triggering the CI.

dylanexpensify
dylanexpensify previously approved these changes Sep 11, 2026

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

Looks good from a product perspective 👍

Accept main's Rules-revamp tag UI cleanup while preserving appendParentTagsFilter navigation for dependent tags.
@neerajbachani

Copy link
Copy Markdown
Contributor Author

@DylanDylann Friendly bump, thanks!!

@DylanDylann

Copy link
Copy Markdown
Contributor

BUG: GL code doesn't display when creating expenses

Screenshot 2026-09-15 at 17 11 38

@DylanDylann

Copy link
Copy Markdown
Contributor

@neerajbachani Could you point out which bug we're resolving here? I'm no longer able to reproduce the original bug and I tried the test steps from the PR description on both this PR and the main branch, but I didn't see any difference

@neerajbachani

neerajbachani commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @DylanDylann thanks for checking. The bug is specific to dependent multi-level tags (not independent multi-level tags), so the repro depends on importing the CSV with the right settings.

Bug we're fixing (#98488)

On main, after importing dependent multi-level tags with GL columns, you can open tag settings for State / Region / City, but the GL code row is not tappable (interactive is disabled when hasDependentTags is true). You can't edit or save GL codes for those tags. Saving at Region/City levels also fails to resolve the correct tag when duplicate names exist under different parents.

This PR unblocks GL code editing for dependent tags, passes parentTagsFilter through navigation/API, and fixes post-save navigation for level 1+ tags.

Repro steps (please use the CSV from the PR description)

  1. Use a Control workspace.
  2. Go to Workspace → Tags → Import tags → Multi-level tags.
  3. Upload multi-level-dependent-tags-with-gl-codes.csv from the PR description.
  4. On the import settings screen, use these toggles:
    • The first row is the title for each tag list → ON
    • These are independent tags → OFF (this is important — dependent tags)
    • There is a GL code in the adjacent column → ON
2026-09-15_15-58-53
  1. Tap Import and wait for it to finish.

Where to see the difference

  1. Open State → select the tag (e.g. California) OR Open Region → select the tag (e.g. South). OR Open City → select a tag (e.g. Austin).

  2. On each level, open tag settings and try to tap GL code.

On main: the GL code row appears (values from CSV) but is not clickable — you can't open the edit screen or change the value.

Screen.Recording.2026-09-15.at.3.59.09.PM.mov

On this PR: the GL code row is tappable, you can edit and save, and after save you return to tag settings (no NotFound) with the updated value shown.

Screen.Recording.2026-09-11.at.1.43.47.AM.online-video-cutter.com.mp4

End-to-end check

  1. Enable Workspace → Tags → More → Settings → Show GL codes when selecting a tag.
  2. Create an expense and open the dependent tag picker — the edited GL codes show where expected.

If you tested with independent multi-level tags, or without the CSV + settings above, main and this branch can look the same. The bug only shows up with dependent tags imported this way.

@neerajbachani

Copy link
Copy Markdown
Contributor Author

BUG: GL code doesn't display when creating expenses

I think this is more of an expected behavior rather than a bug 😃

I also tried reproducing the same issue with the category GL code, but wasn’t able to reproduce it there either. We can confirm the expected behavior with the Product team, and if they feel this needs to be changed, we can cover it in a follow-up PR

@DylanDylann

Copy link
Copy Markdown
Contributor

@DylanDylann Reload → NotFound reproduces on main/staging for dependent child tags when parentTagsFilter isn't in the URL - main already requires it in DynamicTagSettingsPage lookup. This PR fixes in-session GL edit/save. Happy to a follow up pr that add queryParams: ['parentTagsFilter'] to the tag dynamic routes so back/reload round-trip the filter and keep the current pr scoped

I think we should fix this bug here as well, to make it consistent and ensure it works in the entire flow. I don't believe this would add much extra workload

Settings URL already carries parentTagsFilter via queryParams registration.
buildDynamicRoute inherits it from the base path, so appending again in
navigateToEditGlCode caused createDynamicRoute to throw on duplicate keys.
@neerajbachani

Copy link
Copy Markdown
Contributor Author

@neerajbachani

Uncaught Error: [createDynamicRoute] Query param "parentTagsFilter" exists in both base path and dynamic suffix. This is not allowed.

Done.

@neerajbachani

Copy link
Copy Markdown
Contributor Author

@DylanDylann Reload → NotFound reproduces on main/staging for dependent child tags when parentTagsFilter isn't in the URL - main already requires it in DynamicTagSettingsPage lookup. This PR fixes in-session GL edit/save. Happy to a follow up pr that add queryParams: ['parentTagsFilter'] to the tag dynamic routes so back/reload round-trip the filter and keep the current pr scoped

I think we should fix this bug here as well, to make it consistent and ensure it works in the entire flow. I don't believe this would add much extra workload

Agreed, keeping it in this PR. queryParams: ['parentTagsFilter'] is already on the four tag settings/GL routes so back/reload round-trip the filter. The crash fix above is the missing piece so the full flow is consistent.

@DylanDylann

Copy link
Copy Markdown
Contributor

Not Found page displays after reloading and clicking the back button

Screen.Recording.2026-09-22.at.17.14.20.mov

@DylanDylann

Copy link
Copy Markdown
Contributor

@MelvinBot Could you review this PR carefully?

@MelvinBot

Copy link
Copy Markdown
Contributor

Reviewed statically against e1fc9b8. I could not drive this in a browser on this run, so everything below comes from reading the code — no runtime verification.

🔴 The NotFound-after-reload DylanDylann just filmed is caused by this PR's queryParams registration

parentTagsFilter is now registered on both the tag-settings routes and the GL-code routes, but the URL only ever carries one copy of it. getPathWithoutDynamicSuffix.ts:26-35 deletes every query param registered on the suffix it strips — so peeling workspace-tag-gl-code off the URL also deletes the parentTagsFilter that the tag-settings screen underneath still needs.

That breaks both flows:

  • Workspace flow, after reload: getDynamicRouteAdaptedState.ts:92 rebuilds the stack from the stripped base path, so the settings route below is created without the filter. Back → DynamicTagSettingsPage.tsx:78-92 gets undefined from findPolicyTagEntryByParentFilter → NotFoundPage. Exactly the recording above.
  • Quick-settings flow: useDynamicBackPath.ts:38-41 strips it the same way, so backPath resolves to tag-settings/:orderWeight/:tagName with no filter.

Suggested fix: remove queryParams: ['parentTagsFilter'] from WORKSPACE_TAG_GL_CODE and SETTINGS_TAG_GL_CODE, keep it only on the two tag-settings routes. The GL-code screen still receives it, because getStateForDynamicRoute.ts:62-77 merges the full URL's query into the leaf params, and getPathFromState.ts:158-177 still serializes it from the settings route below. Registering it on the child route is what destroys the parent's copy.

That also answers DylanDylann's open question on goBack: once the GL-code routes stop stripping the param, useDynamicBackPath(WORKSPACE_TAG_GL_CODE.path) returns …/workspace-tag-settings/1/South?parentTagsFilter=… again, so the goBack(isQuickSettingsFlow ? backPath : undefined) special-case at DynamicTagGLCodePage.tsx:56-58 can revert to plain goBack(backPath) for both flows.

🟠 Saving a GL code silently does nothing when the tag can't be resolved

Tag.ts:1276-1280 returns early with no error and no log, and DynamicTagGLCodePage.tsx:74-90 calls goBack() unconditionally. The user sees the edit vanish with no feedback. At minimum add a Log.hmmm; better, don't navigate back when the write didn't happen.

🟠 Cold load of the GL-code page can wipe the stored GL code

The new loading guard at DynamicTagGLCodePage.tsx:92-94 lets the FormProvider render while policyTags is still loading. FormProvider.tsx:436-438 seeds inputValues.glCode once from defaultValue — undefined at that moment — and ignores the later value. So after a refresh the field shows empty, and tapping Save submits '', which differs from the stored code and overwrites it.

This pattern predates the PR, but the PR is what makes this page reachable by deep link for dependent tags. Render a loading state (or return early on isLoadingOnyxValue) instead of the form.

🟡 Smaller items

Details
  • findPolicyTagEntryByParentFilter vs findPolicyTagAtLevel — the author's justification holds: one matches an exact filter string and returns the Onyx key, the other regex-tests a parent path. But tag.rules?.parentTagsFilter ?? tag.parentTagsFilter is now written out in three places (PolicyUtils.ts:1139, PolicyUtils.ts:1171, DynamicWorkspaceViewTagsPage.tsx:129). A shared getTagParentFilter(tag) accessor would settle the duplication complaint cleanly.

  • Lookup behaviour changed for level-0 dependent tags. On main the dependent branch matched by iterating values; now an absent parentTagsFilter falls through to tags[tagName] plus the previousTagName fallback (PolicyUtils.ts:1160-1188). That is only equivalent if top-level dependent tags are always keyed by their own name. Worth confirming against a real imported policy.

  • tagName: policyTagToUpdate.name in the API params (Tag.ts:1347) is a behaviour change for renamed tags — the route's tagName used to be sent. Probably the right call, but it is unrelated to dependent tags and untested.

  • navigateToEditGlCode builds glCodeRoute before the isControlPolicy check and then builds a near-identical route again on the upgrade path (DynamicTagSettingsPage.tsx:117-135). Build it once, after the check.

  • Test gap. The new unit tests cover findPolicyTagEntryByParentFilter and setPolicyTagGLCode, which both pass. Neither covers navigation — and navigation is where both regressions live. A getStateFromPath round-trip test asserting that the settings route underneath …/workspace-tag-settings/1/South/workspace-tag-gl-code?parentTagsFilter=%5ECalifornia%24 still carries parentTagsFilter would have caught this and would guard it going forward.

CI

Only Check independent approval and checklist are red; both are checklist gates, not code failures. Everything else passes.

⚠️ Not verified

I could not run the app in a browser on this run, so the fix above is reasoned from the navigation helpers rather than reproduced. Please confirm with the CSV repro before merging.


view run

Registering the filter on the GL routes made suffix stripping delete the only copy, so dependent tags showed NotFound after reload and back. The form now waits for the tag entry so a cold load cannot save an empty GL code.
@neerajbachani

Copy link
Copy Markdown
Contributor Author

@MelvinBot Thanks, the NotFound-after-reload diagnosis is right. parentTagsFilter was registered on both the tag-settings routes and the GL routes, but the URL only carries one copy. getPathWithoutDynamicSuffix deletes every query param registered on the suffix it strips, so peeling workspace-tag-gl-code also removed the filter the settings screen underneath still needs.

That is why State survived and Region/City did not. Level 0 tags have no parent filter and are keyed by name, so California still resolves after the param is gone. Region and City are same-named children under different parents, so findPolicyTagEntryByParentFilter returns nothing without the filter and the settings page renders NotFound.

Fixed by removing queryParams: ['parentTagsFilter'] from WORKSPACE_TAG_GL_CODE and SETTINGS_TAG_GL_CODE only. It stays on the two tag-settings routes. The GL screen still gets the param because getStateFromPath merges the settings route params into the leaf and getStateForDynamicRoute merges the full URL query onto the GL screen. Added a getPathWithoutDynamicSuffix test that stripping the GL suffix keeps parentTagsFilter on the settings path for both the workspace and quick-settings flows.

Also stopped the GL form from mounting until the tag entry exists, so a cold load no longer seeds glCode from undefined and a later Save cannot overwrite the stored code with ''.

Left the workspace goBack(undefined) as-is. Once the param survives the strip, stack pop lands on settings with the filter, so unifying that call is not required for this bug.

On the smaller notes: the early return in setPolicyTagGLCode is only a guard for a missing tag, not the reload path. The level-0 name fallback is intentional and is why State works. The shared getTagParentFilter helper, the tagName API param, and building the GL route once are unrelated to this bug, so I did not change them here.

@neerajbachani

Copy link
Copy Markdown
Contributor Author

Not Found page displays after reloading and clicking the back button

Screen.Recording.2026-09-22.at.17.14.20.mov

Thanks @DylanDylann for flagging this, It's fixed now

@DylanDylann

DylanDylann commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

@neerajbachani How about this? #100885 (comment)

Screenshot 2026-09-23 at 17 18 07

@DylanDylann

DylanDylann commented Sep 23, 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
Screen.Recording.2026-09-25.at.12.28.13.mov
Android: mWeb Chrome
Screen.Recording.2026-09-25.at.12.28.56.mov
iOS: HybridApp
Screen.Recording.2026-09-25.at.12.27.50.mov
iOS: mWeb Safari
Screen.Recording.2026-09-25.at.12.28.36.mov
MacOS: Chrome / Safari
Screen.Recording.2026-09-25.at.12.24.01.mov

@neerajbachani

Copy link
Copy Markdown
Contributor Author

@neerajbachani How about this? #100885 (comment)

@DylanDylann commented the repro steps here #100885 (comment)

The NotFound I hit was right after Save or Back, with no reload.

goBack(backPath) rebuilds the settings URL by stripping workspace-tag-gl-code. That rebuilt URL dropped parentTagsFilter. State still resolved because level 0 tags are keyed by name. Region and City did not: same display names exist under different parents, so findPolicyTagEntryByParentFilter returned nothing and the settings page rendered NotFound. The GL code had already saved. Going back from NotFound showed the updated value.

Repro on the old goBack(backPath): Control workspace, import the dependent State/Region/City CSV, open a Region or City tag (not State), open GL code, tap Save. NotFound. Header back does the same.

Workspace goBack(undefined) pops the settings screen that is already on the stack, which still has parentTagsFilter in its params. Quick settings still uses backPath.

@DylanDylann

Copy link
Copy Markdown
Contributor

@neerajbachani Step 9 looks unrelated to this change

Step 9: Create an expense, open the tag picker with Show GL codes when selecting a tag enabled, and confirm GL codes appear/search for dependent tags as expected

@neerajbachani

Copy link
Copy Markdown
Contributor Author

@neerajbachani Step 9 looks unrelated to this change

Step 9: Create an expense, open the tag picker with Show GL codes when selecting a tag enabled, and confirm GL codes appear/search for dependent tags as expected

@DylanDylann updated

@melvin-bot
melvin-bot Bot requested a review from blimpich September 25, 2026 07:34
@blimpich
blimpich merged commit 9af53a2 into Expensify:main Sep 25, 2026
37 of 40 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

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

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/blimpich in version: 9.4.95-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor

Yes, help site changes are needed. I created a draft PR: Docs updates for E/A#100885 Add GL code steps for dependent multi-level tags.

It updates Create and Manage Expense Tags to:

  • Say GL codes work on every level of dependent and independent multi-level tags.
  • Add a step to select the tag list (level) before selecting a tag.
  • Explain that dependent tags with the same name under different parents keep separate GL codes.
  • Fix the field label from GL Code to GL code to match the UI.
  • Add the FAQ Can I add GL codes to dependent multi-level tags?

I added the HelpDot label. I couldn't assign the PR to you because GitHub rejected the assignment ('neerajbachani' not found). This usually happens when an account has read-only access. Commenting on the docs PR may let an Expensify employee assign you.

@neerajbachani, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review


view run

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/Julesssss in version: 9.4.95-4 🚀

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

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.

6 participants