From 324ebc9dbddb7401f17c6ad4c0710cb8504be30e Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:51:45 -0700 Subject: [PATCH] fix(new-workspace): keep dropdown visible while typing within settled qu Hold the last search results while the user extends or trims the query, only hiding them when the query diverges completely. This prevents the dropdown from flashing empty between debounced keystrokes and removes the guard that made provider rows unselectable during typing. --- .../new-workspace/SmartWorkspaceNameField.tsx | 28 +++---------- .../smart-workspace-source-results.test.ts | 39 +++++++++++++++++++ .../smart-workspace-source-results.ts | 17 ++++++-- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/renderer/src/components/new-workspace/SmartWorkspaceNameField.tsx b/src/renderer/src/components/new-workspace/SmartWorkspaceNameField.tsx index 4d65c87bd47..5a1ad4258c3 100644 --- a/src/renderer/src/components/new-workspace/SmartWorkspaceNameField.tsx +++ b/src/renderer/src/components/new-workspace/SmartWorkspaceNameField.tsx @@ -1081,7 +1081,7 @@ export default function SmartWorkspaceNameField({ } }, [rows]) - // Why: source rows lag debouncedQuery, so keep Enter off a stale row. + // Why: live input leads debounced search; freeze highlight until the query catches up. const valueWithinSourceLimit = isSmartWorkspaceSourceQueryWithinLimit(value) const debouncedQueryWithinSourceLimit = isSmartWorkspaceSourceQueryWithinLimit(debouncedQuery) const trimmedValue = valueWithinSourceLimit ? value.trim() : '' @@ -1151,10 +1151,8 @@ export default function SmartWorkspaceNameField({ const handleSelect = useCallback( (row: RowEntry) => { - // Why: provider rows can lag the live input; typed-name / create-branch stay safe. - if (isQueryStale && row.kind !== 'use-name' && row.kind !== 'create-branch') { - return - } + // Why: select what is shown — held provider rows stay visible while the + // query is ahead of debounce, so blocking them made click/Enter no-ops. if (row.kind === 'use-name' || row.kind === 'create-branch') { // Why: "create new branch" has no ref to base from, so it uses the typed-name path (default base). onValueChange(row.name) @@ -1170,14 +1168,7 @@ export default function SmartWorkspaceNameField({ } setOpen(false) }, - [ - isQueryStale, - onBranchSelect, - onGitHubItemSelect, - onGitLabItemSelect, - onLinearIssueSelect, - onValueChange - ] + [onBranchSelect, onGitHubItemSelect, onGitLabItemSelect, onLinearIssueSelect, onValueChange] ) const applyEmojiReplacement = useCallback( @@ -1609,19 +1600,12 @@ export default function SmartWorkspaceNameField({ } if (open && rows.length > 0) { const row = rows.find((entry) => entry.value === resolvedCommandValue) - if ( - row && - !( - isQueryStale && - row.kind !== 'use-name' && - row.kind !== 'create-branch' - ) - ) { + if (row) { event.preventDefault() handleSelect(row) return } - // No selectable row (stale provider arm); fall through to onPlainEnter. + // No highlighted row; fall through to onPlainEnter so the keypress isn't inert. } onPlainEnter?.() } diff --git a/src/renderer/src/components/new-workspace/smart-workspace-source-results.test.ts b/src/renderer/src/components/new-workspace/smart-workspace-source-results.test.ts index 8fd9744ded2..1a3709686f0 100644 --- a/src/renderer/src/components/new-workspace/smart-workspace-source-results.test.ts +++ b/src/renderer/src/components/new-workspace/smart-workspace-source-results.test.ts @@ -128,6 +128,45 @@ describe('Branch source results', () => { ).toEqual([{ refName: 'origin/feature', localBranchName: 'feature' }]) }) + it('keeps the last branch results while the user trims a prefix of the settled query', () => { + expect( + getVisibleBranchResults({ + mode: 'branches', + value: 'fe', + selectedRepoId: 'repo-1', + resultRepoId: 'repo-1', + resultQuery: 'feat', + branches: [{ refName: 'origin/feature', localBranchName: 'feature' }] + }) + ).toEqual([{ refName: 'origin/feature', localBranchName: 'feature' }]) + }) + + it('hides held branch results when the live query diverges from the settled query', () => { + expect( + getVisibleBranchResults({ + mode: 'branches', + value: 'bug', + selectedRepoId: 'repo-1', + resultRepoId: 'repo-1', + resultQuery: 'feat', + branches: [{ refName: 'origin/feature', localBranchName: 'feature' }] + }) + ).toEqual([]) + }) + + it('keeps held branch results across case-only edits of a prefix query', () => { + expect( + getVisibleBranchResults({ + mode: 'branches', + value: 'Feat', + selectedRepoId: 'repo-1', + resultRepoId: 'repo-1', + resultQuery: 'feat', + branches: [{ refName: 'origin/feature', localBranchName: 'feature' }] + }) + ).toEqual([{ refName: 'origin/feature', localBranchName: 'feature' }]) + }) + it('uses stable cmdk values for typed-text actions', () => { expect( buildSmartWorkspaceSourceRows({ diff --git a/src/shared/new-workspace/smart-workspace-source-results.ts b/src/shared/new-workspace/smart-workspace-source-results.ts index 127573ddd63..6e6d30c8859 100644 --- a/src/shared/new-workspace/smart-workspace-source-results.ts +++ b/src/shared/new-workspace/smart-workspace-source-results.ts @@ -99,9 +99,20 @@ export function getVisibleBranchResults({ if (!selectedRepoId || resultRepoId !== selectedRepoId || resultQuery === null) { return [] } - // Why: hold the last settled branch list while the user types ahead — blanking - // on query mismatch flashed the dropdown empty between keystrokes. - if (value.trim() === '' && resultQuery !== '') { + const currentQuery = value.trim() + // Why: hold the last settled list while the user extends/trims the query so the + // dropdown does not blank between debounced keystrokes. Drop the hold when the + // query diverges (e.g. "feat" → "bug") so unrelated rows do not linger. + if (currentQuery === '') { + return resultQuery === '' ? branches : [] + } + const currentQueryKey = currentQuery.toLowerCase() + const resultQueryKey = resultQuery.toLowerCase() + if ( + resultQueryKey !== currentQueryKey && + !currentQueryKey.startsWith(resultQueryKey) && + !resultQueryKey.startsWith(currentQueryKey) + ) { return [] } return branches