Repository navigation
[$250] [Due for payment 2026-04-30] Search - The workspace with the same name is not displayed in the autocomplete #82304
Description
Activity
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributorDailyKSv2KSv2BugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on Feb 12, 2026 - addedHelp WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributors
on Feb 12, 2026 Triggered auto assignment to Contributor-plus team member for initial proposal review - @aimane-chnaif (
External)Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
Triggered auto assignment to @twisterdotcom (
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.Proposal
Please re-state the problem that we are trying to solve in this issue.
The workspace with the same name is not displayed in the autocomplete.
What is the root cause of that problem?
The root cause is in the
SearchAutocompleteListcomponent. When filtering workspaces for autocomplete suggestions, the code uses workspace names (which can be duplicated) instead of workspace IDs (which are unique) to determine which workspaces have already been selected.The
alreadyAutocompletedKeysSet is created fromrange.value.toLowerCase()which contains the workspace name:
App/src/components/Search/SearchAutocompleteList.tsx
Lines 370 to 376 in b529010
const alreadyAutocompletedKeys = new Set( ranges .filter((range) => { return autocompleteKey && range.key === autocompleteKey; }) .map((range) => range.value.toLowerCase()), ); Then in the
POLICY_IDcase, the filter checks againstworkspace.name.toLowerCase():
App/src/components/Search/SearchAutocompleteList.tsx
Lines 597 to 609 in b529010
case CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID: { const filteredPolicies = workspaceList .filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(workspace.name.toLowerCase())) .sort() .slice(0, 10); return filteredPolicies.map((workspace) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.POLICY_ID, text: workspace.name, autocompleteID: workspace.id, mapKey: CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID, })); } When two workspaces have the same name, selecting one workspace causes the other to be filtered out because they share the same name.
What changes do you think we should make in order to solve the problem?
We should modify the
POLICY_IDcase to build a Set of already-selected workspace IDs instead of names, then filter by checking ifworkspace.idis already in that Set.case CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID: { + const alreadyAutocompletedPolicyIDs = new Set( + ranges + .filter((range) => range.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID) + .flatMap((range) => + workspaceList.filter((workspace) => workspace.name.toLowerCase() === range.value.toLowerCase()).map((workspace) => workspace.id), + ), + ); const filteredPolicies = workspaceList - .filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(workspace.name.toLowerCase())) + .filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedPolicyIDs.has(workspace.id)) .sort() .slice(0, 10); return filteredPolicies.map((workspace) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.POLICY_ID, text: workspace.name, autocompleteID: workspace.id, mapKey: CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID, })); }What alternative solutions did you explore? (Optional)
An alternative would be to pass the
substitutionMapprop (which containsmapKey -> autocompleteIDmappings) intoSearchAutocompleteListand use it directly to get the IDs of already-selected workspaces. However, this would require modifying the component's interface and all its consumers, making it a more invasive change.🚨 Edited by proposal-police: This proposal was edited at 2026-02-18 12:01:13 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
The workspace with the same name is not displayed in the autocomplete. With 3+ workspaces sharing the same name, the issue is more visible: after selecting one or more "A's Workspace" entries, the remaining same-name workspaces still do not appear correctly, or the wrong ones are excluded.
What is the root cause of that problem?
In
SearchAutocompleteList.tsx, the workspace (POLICY_ID) autocomplete excludes already-selected options using workspace name viaalreadyAutocompletedKeys.has(workspace.name.toLowerCase()). That set is built from the display values already present in the query (e.g. "My Workspace"). So after the user selects one workspace named "My Workspace", every workspace whose name matches is treated as "already selected" and filtered out. The second (and third, etc.) workspace with the same name is therefore hidden even though it is a different policy (different ID).Additionally, the substitution map used to store selected workspace IDs is keyed only by display name (
policyID:A's Workspace). When the user selects a second or third "A's Workspace", the new selection overwrites the same key, so only one policy ID is ever stored. As a result:- The actual search query (after substituting names with IDs) only ever targets one workspace.
- Autocomplete cannot exclude by ID because it only has one ID in the map; it cannot distinguish which same-name workspaces are already selected when there are 3+.
So the root cause has two parts:
- Exclusion by name instead of ID — filtering uses
alreadyAutocompletedKeys.has(workspace.name.toLowerCase()), so all workspaces with that name are hidden. - Single substitution per name — the substitution map has only one entry per
(filterKey, value)(e.g.policyID:A's Workspace), so multiple same-name selections overwrite each other and only one policy ID is kept.
What changes do you think we should make in order to solve the problem?
Exclude workspace options by policy ID (when substitution data is available) instead of by name, and support multiple selections of the same display name so that 3+ workspaces with the same name can each be selected and stored correctly.
-
Index-based substitution keys — When the same filter+value appears multiple times in the query (e.g.
workspace:"A's Workspace", "A's Workspace", "A's Workspace"), store each selection under a unique key: first occurrence uses the existing key (e.g.policyID:A's Workspace), subsequent occurrences usepolicyID:A's Workspace:1,policyID:A's Workspace:2, etc. This allows multiple policy IDs for the same display name and correct query substitution for each occurrence. -
SearchAutocompleteList — Accept an optional
autocompleteSubstitutionsprop. For the POLICY_ID (workspace) case, build a set of already-selected policy IDs from the current query using the substitution map (with index-based keys). Filter out workspaces whoseidis in that set. Fall back to name-based exclusion when no substitutions are available. -
SearchRouter / SearchPageHeaderInput — When the user selects a workspace from autocomplete, parse the new query (after appending the selected workspace name), determine the occurrence index for that filter+value, and store the substitution under the corresponding index-based key so we do not overwrite previous same-name selections.
-
getQueryWithSubstitutions / getUpdatedSubstitutionsMap — Use the same index-based key scheme when substituting query text with IDs and when pruning the substitution map so that each occurrence of the same workspace name is correctly mapped to its chosen policy ID.
Implemented changes (for PR)
The following changes were made to fix the issue and support 3+ workspaces with the same name.
1.
App/src/components/Search/SearchRouter/getQueryWithSubstitutions.ts- Added
getSubstitutionMapKeyWithIndex(filterKey, value, index): for index0returns the existing key (e.g.policyID:A's Workspace); for index1, 2, ...returnspolicyID:A's Workspace:1,policyID:A's Workspace:2, etc. - Updated
getQueryWithSubstitutions: assigns an occurrence index to each range (perkey+value), then looks up substitution using the index-based key so each occurrence in the query is replaced by the correct policy ID. Keeps backward compatibility by falling back to the non-indexed key for index0. - Exported
getSubstitutionMapKeyWithIndexfor use in other modules.
2.
App/src/components/Search/SearchRouter/getUpdatedSubstitutionsMap.ts- Updated to assign an occurrence index to each range when building the updated map from the current query.
- Preserves all index-based keys (
baseKey,baseKey:1,baseKey:2, ...) when pruning so that multiple same-name workspace selections remain in the map and are not dropped.
3.
App/src/components/Search/SearchRouter/SearchRouter.tsx- Imported
parsefrom the autocomplete parser andgetSubstitutionMapKeyWithIndexfromgetQueryWithSubstitutions. - When the user selects an autocomplete suggestion (workspace): after building the new query string, parses it and counts how many ranges share the same filter key and value; sets the substitution key for the last occurrence (index = count − 1) using
getSubstitutionMapKeyWithIndexso the new selection is stored under a unique key and does not overwrite previous same-name selections. - Passes
autocompleteSubstitutions={autocompleteSubstitutions}toSearchAutocompleteList.
4.
App/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx- Imported
parsefrom the autocomplete parser,getSubstitutionMapKeyWithIndex, andSearchFilterKeytype. - Applied the same index-based substitution key logic when the user selects a workspace from autocomplete (parse new query, compute index, set substitution with index-based key).
- Passes
autocompleteSubstitutions={autocompleteSubstitutions}to bothSearchAutocompleteListusages.
5.
App/src/components/Search/SearchAutocompleteList.tsx- Added optional prop
autocompleteSubstitutions?: SubstitutionMapand importedSubstitutionMapandgetSubstitutionMapKeyWithIndex. - POLICY_ID (workspace) case:
- Builds
alreadySelectedPolicyIdsby iterating over ranges with key POLICY_ID, computing the same occurrence index per(key, value), and reading the policy ID fromautocompleteSubstitutionsusing the index-based key (with fallback for index0to the non-indexed key). - Filters workspaces with
!alreadySelectedPolicyIds.has(workspace.id)when substitutions are available, so only already-selected policies are excluded and all other workspaces (including 3+ with the same name) remain in the list until selected. - Falls back to the existing name-based exclusion (
alreadyAutocompletedKeys.has(workspace.name.toLowerCase())) whenautocompleteSubstitutionsis not provided or yields no IDs.
- Builds
With these changes, selecting multiple workspaces with the same name (e.g. 3× "A's Workspace") stores each selection under a distinct key, the search query substitutes each occurrence with the correct policy ID, and the autocomplete list hides only the workspaces that are already in the query (by ID), so the remaining same-name options stay visible for selection.
PR Link
Demo
Screen.Recording.2026-02-18.at.4.59.03.PM.mov
github-actions commented
on Feb 12, 2026 on Feb 12, 2026 – with GitHub ActionsContributorMore actions⚠️ @abbasifaizan70 Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.Reacted by Faizan Shoukat AbbasiProposal
Please re-state the problem that we are trying to solve in this issue.
When two workspaces share the same name, the autocomplete list for the
workspace:filter only allows selecting one of them. After selecting the first workspace and adding a comma to select another, the second workspace with the same name does not appear in the autocomplete suggestions.What is the root cause of that problem?
The
alreadyAutocompletedKeysset is built from the parsed queryrangesusing the display text (range.value) instead of the unique ID of each item:const alreadyAutocompletedKeys = new Set( ranges .filter((range) => { return autocompleteKey && range.key === autocompleteKey; }) .map((range) => range.value.toLowerCase()), );
App/src/components/Search/SearchAutocompleteList.tsx
Lines 371 to 377 in 4e0897a
ranges .filter((range) => { return autocompleteKey && range.key === autocompleteKey; }) .map((range) => range.value.toLowerCase()), ); For the
POLICY_IDcase,range.valueis the workspace name. When two workspaces have the same name (e.g., "My Workspace"), selecting the first one adds"my workspace"toalreadyAutocompletedKeys. The filter then excludes ALL workspaces matching that name:case CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID: { const filteredPolicies = workspaceList .filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(workspace.name.toLowerCase())) .sort() .slice(0, 10);
App/src/components/Search/SearchAutocompleteList.tsx
Lines 599 to 611 in 4e0897a
.filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(workspace.name.toLowerCase())) .sort() .slice(0, 10); return filteredPolicies.map((workspace) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.POLICY_ID, text: workspace.name, autocompleteID: workspace.id, mapKey: CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID, })); } case CONST.SEARCH.SYNTAX_FILTER_KEYS.ACTION: { const filteredActionTypes = Object.values(CONST.SEARCH.ACTION_FILTERS).filter((actionType) => { The actual unique IDs are already tracked in
autocompleteSubstitutions(aSubstitutionMapof{filterKey:displayValue → uniqueID}) insideSearchPageHeaderInput, but this data is never passed down toSearchAutocompleteList:App/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx
Lines 92 to 93 in 4e0897a
const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState<SubstitutionMap>({}); const [isAutocompleteListVisible, setIsAutocompleteListVisible] = useState(false); App/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx
Lines 293 to 296 in 4e0897a
const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; setAutocompleteSubstitutions(substitutions); } What changes do you think we should make in order to solve the problem?
Pass
autocompleteSubstitutionsdown fromSearchPageHeaderInputtoSearchAutocompleteList, then use it to computealreadyAutocompletedKeysbased on unique IDs instead of display text for filter types that supportautocompleteID(likePOLICY_ID).- In
src/components/Search/SearchAutocompleteList.tsx, addautocompleteSubstitutionstoSearchAutocompleteListProps:
type SearchAutocompleteListProps = { // ... existing props ... /** Map of autocomplete substitutions (filterKey:displayValue → uniqueID) */ autocompleteSubstitutions?: SubstitutionMap; };
App/src/components/Search/SearchAutocompleteList.tsx
Lines 71 to 116 in 4e0897a
type SearchAutocompleteListProps = { /** Value of TextInput */ autocompleteQueryValue: string; /** Callback to trigger search action * */ handleSearch: (value: string) => void; /** An optional item to always display on the top of the router list */ searchQueryItem?: SearchQueryItem; /** Any extra sections that should be displayed in the router list. */ getAdditionalSections?: GetAdditionalSectionsCallback; /** Callback to call when an item is clicked/selected */ onListItemPress: (item: OptionData | SearchQueryItem) => void; /** Callback to call when user did not click an item but still text query should be changed */ setTextQuery: (item: string) => void; /** Callback to call when the list of autocomplete substitutions should be updated */ updateAutocompleteSubstitutions: (item: SearchQueryItem) => void; /** Whether to subscribe to KeyboardShortcut arrow keys events */ shouldSubscribeToArrowKeyEvents?: boolean; /** Callback to highlight (e.g. scroll to) the first matched item in the list. */ onHighlightFirstItem?: () => void; /** Ref for textInput */ textInputRef?: React.RefObject<AnimatedTextInputRef | null>; /** Personal details */ personalDetails: OnyxEntry<PersonalDetailsList>; /** Reports */ reports: OnyxCollection<Report>; /** All feeds */ allFeeds: Record<string, CardFeeds | undefined> | undefined; /** All cards */ allCards: CardList | undefined; /** Reference to the outer element */ ref?: ForwardedRef<SelectionListHandle>; }; - In the same file, when computing
alreadyAutocompletedKeys, resolve the values to their unique IDs using the substitution map. For thePOLICY_IDcase, filter byworkspace.idagainst the set of already-selected IDs instead of filtering byworkspace.name.
const alreadyAutocompletedKeys = new Set( ranges .filter((range) => autocompleteKey && range.key === autocompleteKey) .map((range) => { const mapKey = getSubstitutionMapKey(range.key, range.value); return autocompleteSubstitutions?.[mapKey] ?? range.value.toLowerCase(); }), ); // ... case CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID: { const filteredPolicies = workspaceList .filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(workspace.id)) .sort() .slice(0, 10);
App/src/components/Search/SearchAutocompleteList.tsx
Lines 371 to 377 in 4e0897a
ranges .filter((range) => { return autocompleteKey && range.key === autocompleteKey; }) .map((range) => range.value.toLowerCase()), ); App/src/components/Search/SearchAutocompleteList.tsx
Lines 599 to 611 in 4e0897a
.filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(workspace.name.toLowerCase())) .sort() .slice(0, 10); return filteredPolicies.map((workspace) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.POLICY_ID, text: workspace.name, autocompleteID: workspace.id, mapKey: CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID, })); } case CONST.SEARCH.SYNTAX_FILTER_KEYS.ACTION: { const filteredActionTypes = Object.values(CONST.SEARCH.ACTION_FILTERS).filter((actionType) => { After this, we need to refactor all other cases to use the value we mapped to
autocompleteIDto filter out withalreadyAutocompletedIDsinstead, because the bug also happens for other keys example likeIn, we need to checkchat.reportIDis included inalreadyAutocompletedKeysinstead ofchat.text- In
src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx, passautocompleteSubstitutionsto bothSearchAutocompleteListrender sites:
<SearchAutocompleteList autocompleteQueryValue={autocompleteQueryValue} // ... existing props ... autocompleteSubstitutions={autocompleteSubstitutions} />
App/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx
Lines 425 to 438 in 4e0897a
autocompleteQueryValue={autocompleteQueryValue} handleSearch={handleSearchAction} searchQueryItem={searchQueryItem} onListItemPress={onListItemPress} setTextQuery={setTextAndUpdateSelection} updateAutocompleteSubstitutions={updateAutocompleteSubstitutions} ref={listRef} personalDetails={personalDetails} reports={reports} allCards={nonPersonalAndWorkspaceCards} allFeeds={allFeeds} textInputRef={textInputRef} /> </View> App/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx
Lines 500 to 514 in 4e0897a
autocompleteQueryValue={autocompleteQueryValue} handleSearch={handleSearchAction} searchQueryItem={searchQueryItem} onListItemPress={onListItemPress} setTextQuery={setTextAndUpdateSelection} updateAutocompleteSubstitutions={updateAutocompleteSubstitutions} ref={listRef} shouldSubscribeToArrowKeyEvents={isAutocompleteListVisible} personalDetails={personalDetails} reports={reports} allCards={nonPersonalAndWorkspaceCards} allFeeds={allFeeds} textInputRef={textInputRef} /> </View> What alternative solutions did you explore? (Optional)
We can only calculate manually
alreadyAutocompletedIDsfor some special search key likepolicy_id,in, ...- In
@aimane-chnaif Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
76 remaining items
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributorand removedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributor
on May 1, 2026 - addedHelp WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributors
on May 1, 2026 Current assignee @aimane-chnaif is eligible for the External assigner, not assigning anyone new.
- changed the title
[-][Due for payment 2026-04-30] Search - The workspace with the same name is not displayed in the autocomplete[/-][+][$250] [Due for payment 2026-04-30] Search - The workspace with the same name is not displayed in the autocomplete[/+]on May 1, 2026 Job added to Upwork: https://www.upwork.com/jobs/~022050309179163591879
@abbasifaizan70 @aimane-chnaif can you please accept the job below? Please reply here and tag me once you have.
https://www.upwork.com/jobs/~022050309179163591879^ Test case created
@mallenexpensify Offer accepted and submitted for payment approval. Thanks
Payment Summary
Contributor: @abbasifaizan70 paid $250 via Upwork
Contributor+: @aimane-chnaif paid $250 via UpworkReacted by Faizan Shoukat Abbasi@mallenexpensify accepted offer
@aimane-chnaif paid, summary updated above. Thx.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
Version Number: v9.3.18-1
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/TR-5134/folder/13176922/47701683/1186708606?sort=%2Bidentifier
Email or phone of affected tester (no customers): n/a
Issue reported by: Applause Internal Team
Bug source: Regression TC Execution
Device used: Mac 15.4.1 Safari, Chrome, iPhone 13 iOS 26.2.1
App Component: Search
Action Performed:
Expected Result:
Another workspace with the same name should appear in the autocomplete for the workspaces
Actual Result:
The workspace with the same name is not displayed in the autocomplete
Workaround:
Unknown
Platforms:
Screenshots/Videos
1.mp4
View all open jobs on GitHub
Issue Owner
Current Issue Owner: @mallenexpensifyUpwork Automation - Do Not Edit