You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$250] Multi-level categories/tags with colons are not displayed with hierarchical indentation in certain places in NewDot #82068
Version Number: N/A Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack: N/A Email or phone of affected tester (no customers): N/A Logs: N/A Expensify/Expensify Issue URL: N/A Issue reported by:@brianlee-expensify Slack conversation:https://expensify.slack.com/archives/C05LX9D6E07/p1770752446067979
Action Performed:
Create or use a workspace with multi-level categories that use colons as separators (e.g. Primary:Secondary:Tertiary)
Open an expense and view the category selector in NewDot
Expected Result:
Multi-level categories should be displayed with hierarchical indentation (like in Classic/OldDot), where sub-categories are visually nested under their parent categories. For example:
Primary
Secondary
Tertiary
Professional Services
Actual Result:
Categories are displayed as flat, colon-separated strings (e.g. Primary:Secondary:Tertiary), making them hard to read — especially for long or deeply nested category names. The full text is also difficult to read at the individual expense level because the display area is too narrow for long concatenated names.
In Classic/OldDot, the dropdown properly parses the colons and renders sub-categories with indentation, which is significantly more readable.
Workaround:
Users can hover over each option in NewDot to see the full text, but this is far less efficient than the Classic hierarchical display.
Platforms:
Android: App
Android: mWeb Chrome
iOS: App
iOS: mWeb Safari
iOS: mWeb Chrome
Windows: Chrome
MacOS: Chrome / Safari
Screenshots/Videos
NewDot (current — flat colon-separated display):
Long category names like General and Administrative Expenses/Other Ge... are truncated and hard to read.
Classic/OldDot (expected — hierarchical indentation):
Categories with colons are parsed and displayed with visual nesting:
changed the title [-]Multi-level categories/tags with colons are not displayed with hierarchical indentation in NewDot[/-][+][$250] Multi-level categories/tags with colons are not displayed with hierarchical indentation in NewDot[/+]on Feb 11, 2026
Please re-state the problem that we are solving in this issue in your own words.
When multi-level categories use colons as separators (e.g., Primary:Secondary:Tertiary), they are displayed as flat concatenated strings in NewDot's category selector. This makes deeply nested categories hard to read, especially when names are long and get truncated. Classic/OldDot correctly parses the colons and displays sub-categories with visual indentation.
What is the root cause of that problem?
The category selector renders category names as-is without parsing the colon delimiter to determine hierarchy depth. There's no logic to split on : and apply indentation styling based on the resulting segments.
What changes do you think we should make in order to solve the problem?
In the category option rendering logic (likely in src/components/OptionsList or the category-specific selector), split the category name by : to determine the hierarchy level
Calculate indentation depth based on the number of segments (e.g., Primary:Secondary = depth 2)
Display only the final segment as the visible text, with left padding proportional to depth
Ensure the full path is still used as the value/key for selection
What alternative solutions did you explore? (Optional)
Tooltip-only approach: Keep flat display but show full hierarchy in tooltip. Rejected because it doesn't match Classic behavior and is less efficient for users.
Tree component: Build a collapsible tree structure. Rejected as overkill — simple indentation matches Classic behavior.
When categories come from accounting integrations (e.g., QuickBooks), they use : without a space — e.g., "Primary:Secondary:Tertiary". Since "Primary:Secondary:Tertiary".split(': ') produces ["Primary:Secondary:Tertiary"] (a single-element array), no hierarchy is detected and no indentation is applied. The same issue affects the sortCategories() function which also splits by ': '.
OldDot/Classic handles this correctly because it parses : as the separator regardless of spacing.
What changes do you think we should make in order to solve the problem?
We need to replace the literal CONST.PARENT_CHILD_SEPARATOR split with a regex /\s*:\s*/ that handles both : and : and add a splitCategoryName(name) helper that splits by /\s*:\s*/ and provides a getSearchText(index, isLeaf) method. For leaf entries it returns the original option.name (preserving the backend key for matching). For parent entries it finds the Nth colon position in the original string and returns substring(0, colonPos).trim(), preserving the original separator style in the key.
Multi-level categories that use colons as separators (e.g., Primary:Secondary:Tertiary) are displayed as flat, colon-separated strings in the category picker instead of with hierarchical indentation. This affects:
Recently used categories
Selected categories
Search results
The "All" categories section correctly displays hierarchical indentation.
Root Cause
The issue is in src/libs/CategoryOptionListUtils.ts. The getCategoryOptionTree() function has an isOneLine parameter that controls whether to build the tree hierarchy with indentation:
When isOneLine = true: The function skips tree building and returns the full category name without indentation
When isOneLine = false: The function properly parses the colon separator and builds indented display names
Only the "All" section (lines 171, 204) correctly passes isOneLine = false.
Root Cause Verification Checklist
This is the actual root cause, not a symptom - The isOneLine parameter directly controls whether indentation is applied
Compared with working similar flows - The "All" section works correctly because it passes isOneLine = false
All call sites of affected functions considered - All four calls to getCategoryOptionTree() in getCategoryListSections() are affected
For detection logic: function receives complete data - Yes, the data is complete but the flag prevents processing
Minimal Fix Checklist
Fix is at the earliest point of failure (not a workaround) - Yes, the fix is in the data preparation function
No new parameters added to existing functions - Yes, just changing boolean values
No new utility functions created - Yes
No refactoring of unrelated code - Yes
Change follows existing patterns exactly - Yes, follows the pattern used by the "All" section
Line count is minimized (prefer 1-line fixes over 10-line fixes) - Yes, 4 lines changed
Working vs Broken Comparison
Aspect
Working Flow ("All" section)
Broken Flow (Selected/Recent/Search)
Flow name
All categories
Selected, Recent, Search
Key condition
isOneLine = false
isOneLine = true
Data passed
Full categories with tree building
Full categories without tree building
Result
Hierarchical indentation
Flat colon-separated strings
Alternative Solutions Considered
Alternative 1: Modify getCategoryOptionTree() to always build tree
Remove the isOneLine parameter entirely and always build the tree hierarchy.
Why rejected: The isOneLine parameter exists for a reason - when searching, users want to see the full category path to distinguish between similarly named subcategories. Changing this behavior globally could break other use cases.
Alternative 2: Add new parameter for indentation only
Add a new parameter like shouldIndent that controls indentation separately from tree building.
Why rejected: This adds complexity without benefit. The existing isOneLine flag is meant to control both tree building and indentation display. The simpler fix is to pass false where we want indentation.
Alternative 3: Post-process in RadioListItem
Handle indentation in the UI component instead of the data layer.
Why rejected: This would require passing additional metadata to the component and would be a more invasive change. The current approach of preparing data in CategoryOptionListUtils is the established pattern.
Solution
Change isOneLine from true to false for the three broken flows in getCategoryListSections():
Line 127: Selected categories when no enabled categories
Line 146: Search results
Line 158: Selected categories section
Line 195: Recent categories section
This ensures all category displays use the same hierarchical indentation logic.
Implementation
Files Modified
src/libs/CategoryOptionListUtils.ts
Key Changes
// Line 127 - Change isOneLine from true to falseconstdata=getCategoryOptionTree(selectedOptionsWithDisabledState,false);// Line 146 - Change isOneLine from true to falseconstdata=getCategoryOptionTree(searchCategories,false);// Line 158 - Change isOneLine from true to falseconstdata=getCategoryOptionTree(selectedOptionsWithDisabledState,false);// Line 195 - Change isOneLine from true to falseconstdata=getCategoryOptionTree(cutRecentlyUsedCategories,false);
Testing Approach
Create a workspace with multi-level categories using colons (e.g., Primary:Secondary:Tertiary)
Create an expense and open the category picker
Verify that:
The "All" section shows hierarchical indentation (already works)
The "Recent" section shows hierarchical indentation (fixed)
The "Selected" section shows hierarchical indentation (fixed)
Search results show hierarchical indentation (fixed)
Verify that selecting a subcategory still works correctly
Verify that the search functionality still filters correctly
Edge Cases
Empty/null states: The function already handles empty arrays correctly
Single-level categories: Will display the same as before (no indentation needed)
Deep nesting (3+ levels): Will display with appropriate indentation levels
Offline/online behavior: No impact, this is a display-only change
Different report types: No impact, this affects the category picker component used across all flows
Performance Impact
Minimal. The tree building logic is already used for the "All" section and performs well. The additional processing for selected/recent categories (typically small sets) will have negligible impact.
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
It's a new feature
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: NA
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
Regression Test Proposal
Test:
Open App
Create or use a workspace with multi-level categories that use colons as separators (e.g. Primary:Secondary:Tertiary)
Open an expense and view the category selector in NewDot
Verify that Multi-level categories will be displayed with hierarchical indentation (like in Classic/OldDot), where sub-categories are visually nested under their parent categories. For example:
Oh
Sorry
I missed this issue
And about payment
We had a few regressions
But this issue was quite complex, with a lot of edge cases
Plus, we expanded the scope of the issue, which required significant time for testing #85448 (comment)
So, if you don't mind, I propose 375$ here
CC: @trjExpensify(Since you proposed updating the UI in a similar way to the OD 😁)
Activity
melvin-bot commentedon Feb 11, 2026
Triggered auto assignment to @heyjennahay (
Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.melvin-bot commentedon Feb 11, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ZhenjaHorbach (
External)[-]Multi-level categories/tags with colons are not displayed with hierarchical indentation in NewDot[/-][+][$250] Multi-level categories/tags with colons are not displayed with hierarchical indentation in NewDot[/+]melvin-bot commentedon Feb 11, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022021404126598547656
imgyf commentedon Feb 11, 2026
Proposal
Please re-state the problem that we are solving in this issue in your own words.
When multi-level categories use colons as separators (e.g.,
Primary:Secondary:Tertiary), they are displayed as flat concatenated strings in NewDot's category selector. This makes deeply nested categories hard to read, especially when names are long and get truncated. Classic/OldDot correctly parses the colons and displays sub-categories with visual indentation.What is the root cause of that problem?
The category selector renders category names as-is without parsing the colon delimiter to determine hierarchy depth. There's no logic to split on
:and apply indentation styling based on the resulting segments.What changes do you think we should make in order to solve the problem?
src/components/OptionsListor the category-specific selector), split the category name by:to determine the hierarchy levelPrimary:Secondary= depth 2)For example:
What alternative solutions did you explore? (Optional)
mukhrr commentedon Feb 11, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Multi-level categories/tags with colons are not displayed with hierarchical indentation - flat text without tree-like shape
What is the root cause of that problem?
The
PARENT_CHILD_SEPARATORconst is defined as:(colon with a trailing space) atsrc/CONST/index.ts.App/src/CONST/index.ts
Line 5718 in 04c98c9
The
getCategoryOptionTree()function insrc/libs/CategoryOptionListUtils.tssplits category names using this separator:App/src/libs/CategoryOptionListUtils.ts
Line 56 in 04c98c9
When categories come from accounting integrations (e.g., QuickBooks), they use
:without a space — e.g., "Primary:Secondary:Tertiary". Since"Primary:Secondary:Tertiary".split(': ')produces ["Primary:Secondary:Tertiary"] (a single-element array), no hierarchy is detected and no indentation is applied. The same issue affects the sortCategories() function which also splits by ': '.App/src/libs/CategoryOptionListUtils.ts
Lines 241 to 242 in 04c98c9
OldDot/Classic handles this correctly because it parses
:as the separator regardless of spacing.What changes do you think we should make in order to solve the problem?
We need to replace the literal
CONST.PARENT_CHILD_SEPARATORsplit with a regex/\s*:\s*/that handles both:and:and add asplitCategoryName(name)helper that splits by/\s*:\s*/and provides agetSearchText(index, isLeaf)method. For leaf entries it returns the originaloption.name(preserving the backend key for matching). For parent entries it finds theNthcolon position in the original string and returnssubstring(0, colonPos).trim(), preserving the original separator style in the key.Also in
sortCategories:What alternative solutions did you explore? (Optional)
HiepPP commentedon Feb 11, 2026
Proposal for Issue #82068
Problem
Multi-level categories that use colons as separators (e.g.,
Primary:Secondary:Tertiary) are displayed as flat, colon-separated strings in the category picker instead of with hierarchical indentation. This affects:The "All" categories section correctly displays hierarchical indentation.
Root Cause
The issue is in
src/libs/CategoryOptionListUtils.ts. ThegetCategoryOptionTree()function has anisOneLineparameter that controls whether to build the tree hierarchy with indentation:isOneLine = true: The function skips tree building and returns the full category name without indentationisOneLine = false: The function properly parses the colon separator and builds indented display namesCurrently,
getCategoryListSections()passesisOneLine = truefor:Only the "All" section (lines 171, 204) correctly passes
isOneLine = false.Root Cause Verification Checklist
isOneLineparameter directly controls whether indentation is appliedisOneLine = falsegetCategoryOptionTree()ingetCategoryListSections()are affectedMinimal Fix Checklist
Working vs Broken Comparison
isOneLine = falseisOneLine = trueAlternative Solutions Considered
Alternative 1: Modify
getCategoryOptionTree()to always build treeRemove the
isOneLineparameter entirely and always build the tree hierarchy.Why rejected: The
isOneLineparameter exists for a reason - when searching, users want to see the full category path to distinguish between similarly named subcategories. Changing this behavior globally could break other use cases.Alternative 2: Add new parameter for indentation only
Add a new parameter like
shouldIndentthat controls indentation separately from tree building.Why rejected: This adds complexity without benefit. The existing
isOneLineflag is meant to control both tree building and indentation display. The simpler fix is to passfalsewhere we want indentation.Alternative 3: Post-process in RadioListItem
Handle indentation in the UI component instead of the data layer.
Why rejected: This would require passing additional metadata to the component and would be a more invasive change. The current approach of preparing data in
CategoryOptionListUtilsis the established pattern.Solution
Change
isOneLinefromtruetofalsefor the three broken flows ingetCategoryListSections():This ensures all category displays use the same hierarchical indentation logic.
Implementation
Files Modified
src/libs/CategoryOptionListUtils.tsKey Changes
Testing Approach
Primary:Secondary:Tertiary)Edge Cases
Performance Impact
Minimal. The tree building logic is already used for the "All" section and performs well. The additional processing for selected/recent categories (typically small sets) will have negligible impact.
72 remaining items
brianlee-expensify commentedon Jun 4, 2026
friendly bump for @yuwenmemon !
ijmalik commentedon Jun 9, 2026
Another friendly bump for @yuwenmemon and @ZhenjaHorbach when you get a quick moment to check the payment tracker issue. Appreciate it!
CC: @brianlee-expensify
melvin-bot commentedon Jun 12, 2026
@yuwenmemon Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
ZhenjaHorbach commentedon Jun 15, 2026
BugZero Checklist:
It's a new feature
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: NA
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
Regression Test Proposal
Test:
Do we agree 👍 or 👎
Zapier Logs
Run ID: 00040eee-9b84-a64e-4e09-50f095b761f4ZhenjaHorbach commentedon Jun 15, 2026
Oh
Sorry
I missed this issue
And about payment
We had a few regressions
But this issue was quite complex, with a lot of edge cases
Plus, we expanded the scope of the issue, which required significant time for testing
#85448 (comment)
So, if you don't mind, I propose 375$ here
CC: @trjExpensify(Since you proposed updating the UI in a similar way to the OD 😁)
trjExpensify commentedon Jun 16, 2026
Works for me. 👍
ZhenjaHorbach commentedon Jun 16, 2026
@mallenexpensify
We are done here!
mallenexpensify commentedon Jun 16, 2026
Payment Summary
Contributor: @ijmalik paid $375 via Upwork
Contributor+: @ZhenjaHorbach due $375 via NewDot
^ Test case created. Thx
garrettmknight commentedon Jun 18, 2026
$375 approved for @ZhenjaHorbach