fix(new-workspace): stop UI flashing when typing ahead of search (#11436)
* fix(new-workspace): stop UI flashing when typing ahead of search Hold branch results while queries settle, show the spinner only on initial load, use stable cmdk values, and guard selections against stale rows. This prevents the highlight from jumping around when typing faster than the debounced search settles. * 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. * fix(new-workspace): align held provider results with live typing Cap prefix hold by length delta, hide GitHub/GitLab/Linear rows when the field is cleared ahead of debounce, and re-sync the cmdk arm when search settles so the highlight cannot lag the resolved selection.
This commit is contained in:
parent
93dfe68d73
commit
8d4e975ff7
|
|
@ -57,6 +57,7 @@ import {
|
|||
getBranchSearchRequest,
|
||||
getSmartWorkspaceEmptyHint,
|
||||
getVisibleBranchResults,
|
||||
getVisibleHeldProviderResults,
|
||||
isSmartWorkspaceSourceQueryWithinLimit,
|
||||
type SmartNameMode,
|
||||
type SmartWorkspaceSourceRow
|
||||
|
|
@ -558,6 +559,11 @@ export default function SmartWorkspaceNameField({
|
|||
return
|
||||
}
|
||||
let stale = false
|
||||
// Why: empty-query search must not briefly paint the previous non-empty result set
|
||||
// once debounce catches a cleared field.
|
||||
if (debouncedQuery.trim() === '') {
|
||||
setGithubItems([])
|
||||
}
|
||||
const directNumber = normalizedGhQuery.directNumber
|
||||
const directLink = parsedGhLink
|
||||
if (directLink !== null && handledCrossRepoUrlRef.current !== debouncedQuery.trim()) {
|
||||
|
|
@ -832,8 +838,8 @@ export default function SmartWorkspaceNameField({
|
|||
return
|
||||
}
|
||||
let stale = false
|
||||
setBranches([])
|
||||
setBranchResultsSource(null)
|
||||
// Why: keep prior branch rows until this request settles; visibility already
|
||||
// holds the last list while the user types ahead of the debounced query.
|
||||
setBranchesLoading(true)
|
||||
void searchRuntimeRepoBaseRefDetails(
|
||||
selectedRepoOwnerSettings,
|
||||
|
|
@ -875,6 +881,10 @@ export default function SmartWorkspaceNameField({
|
|||
let stale = false
|
||||
setLinearLoading(true)
|
||||
const trimmed = debouncedQuery.trim()
|
||||
// Why: empty-query list must not briefly paint the previous non-empty result set.
|
||||
if (trimmed === '') {
|
||||
setLinearIssues([])
|
||||
}
|
||||
const request = trimmed
|
||||
? searchLinearIssues(trimmed, RESULT_LIMIT, { sourceContext: linearSourceContext })
|
||||
: listLinearIssues(
|
||||
|
|
@ -992,6 +1002,10 @@ export default function SmartWorkspaceNameField({
|
|||
setGitlabLoading(true)
|
||||
// Why: thread the typed query so the GitLab API filters MRs by name/number (shouldQueryGitlab already gates oversized queries).
|
||||
const trimmedQuery = debouncedQuery.trim() || undefined
|
||||
// Why: empty-query list must not briefly paint the previous non-empty result set.
|
||||
if (trimmedQuery === undefined) {
|
||||
setGitlabItems([])
|
||||
}
|
||||
void Promise.all(
|
||||
repoBackedSearchTargets.map((target) =>
|
||||
listGitLabMRsForSource({
|
||||
|
|
@ -1051,11 +1065,23 @@ export default function SmartWorkspaceNameField({
|
|||
selectedRepoId: selectedRepo?.id ?? null,
|
||||
value
|
||||
}),
|
||||
githubItems,
|
||||
githubItems: getVisibleHeldProviderResults({
|
||||
items: githubItems,
|
||||
value,
|
||||
debouncedQuery
|
||||
}),
|
||||
gitlabAvailable: gitlabSourceAvailable,
|
||||
gitlabItems,
|
||||
gitlabItems: getVisibleHeldProviderResults({
|
||||
items: gitlabItems,
|
||||
value,
|
||||
debouncedQuery
|
||||
}),
|
||||
linearAvailable,
|
||||
linearIssues,
|
||||
linearIssues: getVisibleHeldProviderResults({
|
||||
items: linearIssues,
|
||||
value,
|
||||
debouncedQuery
|
||||
}),
|
||||
mode,
|
||||
resultLimit: RESULT_LIMIT,
|
||||
value
|
||||
|
|
@ -1063,6 +1089,7 @@ export default function SmartWorkspaceNameField({
|
|||
[
|
||||
branches,
|
||||
branchResultsSource,
|
||||
debouncedQuery,
|
||||
githubItems,
|
||||
gitlabSourceAvailable,
|
||||
gitlabItems,
|
||||
|
|
@ -1081,7 +1108,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() : ''
|
||||
|
|
@ -1115,6 +1142,14 @@ export default function SmartWorkspaceNameField({
|
|||
isQueryStale,
|
||||
sourceIntent
|
||||
})
|
||||
// Why: while isQueryStale, cmdk onValueChange is ignored; re-sync the stored arm
|
||||
// when the query settles so commandValue cannot lag resolvedCommandValue.
|
||||
useEffect(() => {
|
||||
if (isQueryStale || commandValue === resolvedCommandValue) {
|
||||
return
|
||||
}
|
||||
setCommandValue(resolvedCommandValue)
|
||||
}, [commandValue, isQueryStale, resolvedCommandValue])
|
||||
const activeEmojiShortcode = useMemo(
|
||||
() => getActiveWorkspaceEmojiShortcode(value, emojiCursor),
|
||||
[emojiCursor, value]
|
||||
|
|
@ -1144,10 +1179,15 @@ export default function SmartWorkspaceNameField({
|
|||
) ?? null
|
||||
|
||||
const loading = githubLoading || gitlabLoading || branchesLoading || linearLoading
|
||||
const ActiveInputIcon = mode === 'text' ? CaseSensitive : loading ? LoaderCircle : Search
|
||||
// Why: only spin on first load — not on every in-flight refresh while rows stay visible.
|
||||
const showSearchSpinner = loading && searchResultRows.length === 0
|
||||
const ActiveInputIcon =
|
||||
mode === 'text' ? CaseSensitive : showSearchSpinner ? LoaderCircle : Search
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(row: RowEntry) => {
|
||||
// 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)
|
||||
|
|
@ -1413,7 +1453,14 @@ export default function SmartWorkspaceNameField({
|
|||
>
|
||||
<Command
|
||||
value={resolvedCommandValue}
|
||||
onValueChange={setCommandValue}
|
||||
onValueChange={(next) => {
|
||||
// Why: cmdk re-emits when the item list reshapes; ignore while the query
|
||||
// lags so the highlight cannot thrash mid-typing.
|
||||
if (isQueryStale) {
|
||||
return
|
||||
}
|
||||
setCommandValue(next)
|
||||
}}
|
||||
shouldFilter={false}
|
||||
className="overflow-visible bg-transparent"
|
||||
>
|
||||
|
|
@ -1499,7 +1546,7 @@ export default function SmartWorkspaceNameField({
|
|||
<ActiveInputIcon
|
||||
className={cn(
|
||||
'pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground',
|
||||
loading && mode !== 'text' && 'animate-spin'
|
||||
showSearchSpinner && mode !== 'text' && 'animate-spin'
|
||||
)}
|
||||
/>
|
||||
<Input
|
||||
|
|
@ -1593,7 +1640,7 @@ export default function SmartWorkspaceNameField({
|
|||
handleSelect(row)
|
||||
return
|
||||
}
|
||||
// No highlighted row (e.g. cleared stale GitHub/Linear results); fall through to onPlainEnter so the keypress isn't inert.
|
||||
// No highlighted row; fall through to onPlainEnter so the keypress isn't inert.
|
||||
}
|
||||
onPlainEnter?.()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe('resolveSmartWorkspaceCommandValue', () => {
|
|||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'github-12',
|
||||
rows: [row('use-name', 'use-name-fix'), row('github', 'github-12')],
|
||||
rows: [row('use-name', 'use-name'), row('github', 'github-12')],
|
||||
isQueryStale: false,
|
||||
sourceIntent: null
|
||||
})
|
||||
|
|
@ -24,40 +24,51 @@ describe('resolveSmartWorkspaceCommandValue', () => {
|
|||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'github-12',
|
||||
rows: [row('use-name', 'use-name-fix'), row('branch', 'branch-main')],
|
||||
rows: [row('use-name', 'use-name'), row('branch', 'branch-main')],
|
||||
isQueryStale: false,
|
||||
sourceIntent: null
|
||||
})
|
||||
).toBe('use-name-fix')
|
||||
).toBe('use-name')
|
||||
})
|
||||
|
||||
it('uses typed-text rows while source results are stale', () => {
|
||||
it('freezes the current arm while the query is ahead of debounced search', () => {
|
||||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'github-12',
|
||||
rows: [row('use-name', 'use-name-fix'), row('github', 'github-12')],
|
||||
rows: [row('use-name', 'use-name'), row('github', 'github-12')],
|
||||
isQueryStale: true,
|
||||
sourceIntent: null
|
||||
})
|
||||
).toBe('use-name-fix')
|
||||
).toBe('github-12')
|
||||
})
|
||||
|
||||
it('clears selection while stale source-only rows have no typed fallback', () => {
|
||||
it('falls back to typed-text when a frozen arm is no longer rendered', () => {
|
||||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'github-12',
|
||||
rows: [row('github', 'github-12')],
|
||||
rows: [row('use-name', 'use-name'), row('github', 'github-99')],
|
||||
isQueryStale: true,
|
||||
sourceIntent: null
|
||||
})
|
||||
).toBe('')
|
||||
).toBe('use-name')
|
||||
})
|
||||
|
||||
it('falls back to the first provider row when stale with no typed-text', () => {
|
||||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'github-12',
|
||||
rows: [row('github', 'github-99')],
|
||||
isQueryStale: true,
|
||||
sourceIntent: null
|
||||
})
|
||||
).toBe('github-99')
|
||||
})
|
||||
|
||||
it('prefers matching source-intent rows once fresh results arrive', () => {
|
||||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'use-name-123',
|
||||
rows: [row('use-name', 'use-name-123'), row('github', 'github-123')],
|
||||
currentValue: 'use-name',
|
||||
rows: [row('use-name', 'use-name'), row('github', 'github-123')],
|
||||
isQueryStale: false,
|
||||
sourceIntent: 'github'
|
||||
})
|
||||
|
|
@ -65,8 +76,8 @@ describe('resolveSmartWorkspaceCommandValue', () => {
|
|||
|
||||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'use-name-gitlab-url',
|
||||
rows: [row('use-name', 'use-name-gitlab-url'), row('gitlab', 'gitlab-123')],
|
||||
currentValue: 'use-name',
|
||||
rows: [row('use-name', 'use-name'), row('gitlab', 'gitlab-123')],
|
||||
isQueryStale: false,
|
||||
sourceIntent: 'gitlab'
|
||||
})
|
||||
|
|
@ -74,8 +85,8 @@ describe('resolveSmartWorkspaceCommandValue', () => {
|
|||
|
||||
expect(
|
||||
resolveSmartWorkspaceCommandValue({
|
||||
currentValue: 'use-name-eng-123',
|
||||
rows: [row('use-name', 'use-name-eng-123'), row('linear', 'linear-ENG-123')],
|
||||
currentValue: 'use-name',
|
||||
rows: [row('use-name', 'use-name'), row('linear', 'linear-ENG-123')],
|
||||
isQueryStale: false,
|
||||
sourceIntent: 'linear'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import {
|
|||
getBranchSearchRequest,
|
||||
getSmartWorkspaceEmptyHint,
|
||||
getVisibleBranchResults,
|
||||
isSmartWorkspaceSourceQueryWithinLimit
|
||||
getVisibleHeldProviderResults,
|
||||
isSmartWorkspaceSourceQueryWithinLimit,
|
||||
shouldHoldSourceResultsForQuery
|
||||
} from './smart-workspace-source-results'
|
||||
|
||||
describe('Branch source results', () => {
|
||||
|
|
@ -102,7 +104,7 @@ describe('Branch source results', () => {
|
|||
expect(rows).toEqual([])
|
||||
})
|
||||
|
||||
it('hides branch results from a stale Branch-mode query', () => {
|
||||
it('hides branch results after the input is cleared while a prior query is still held', () => {
|
||||
expect(
|
||||
getVisibleBranchResults({
|
||||
mode: 'branches',
|
||||
|
|
@ -115,6 +117,137 @@ describe('Branch source results', () => {
|
|||
).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps the last branch results while the user types ahead of the settled query', () => {
|
||||
expect(
|
||||
getVisibleBranchResults({
|
||||
mode: 'branches',
|
||||
value: 'featu',
|
||||
selectedRepoId: 'repo-1',
|
||||
resultRepoId: 'repo-1',
|
||||
resultQuery: 'feat',
|
||||
branches: [{ refName: 'origin/feature', localBranchName: 'feature' }]
|
||||
})
|
||||
).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('drops a short settled query once the live query grows far beyond a typing delta', () => {
|
||||
// Why: prefix-only hold would keep "f" results under "fix-unrelated-payment-bug".
|
||||
expect(
|
||||
getVisibleBranchResults({
|
||||
mode: 'branches',
|
||||
value: 'fix-unrelated-payment-bug',
|
||||
selectedRepoId: 'repo-1',
|
||||
resultRepoId: 'repo-1',
|
||||
resultQuery: 'f',
|
||||
branches: [{ refName: 'origin/foo', localBranchName: 'foo' }]
|
||||
})
|
||||
).toEqual([])
|
||||
expect(
|
||||
shouldHoldSourceResultsForQuery({ resultQuery: 'f', value: 'fix-unrelated-payment-bug' })
|
||||
).toBe(false)
|
||||
expect(shouldHoldSourceResultsForQuery({ resultQuery: 'feat', value: 'featu' })).toBe(true)
|
||||
expect(shouldHoldSourceResultsForQuery({ resultQuery: 'feat', value: 'feature/x' })).toBe(false)
|
||||
})
|
||||
|
||||
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('hides held provider results immediately when the field is cleared ahead of debounce', () => {
|
||||
expect(
|
||||
getVisibleHeldProviderResults({
|
||||
items: [{ id: 'pr-1' }],
|
||||
value: '',
|
||||
debouncedQuery: 'fix'
|
||||
})
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps held provider results while the user types ahead of debounce', () => {
|
||||
expect(
|
||||
getVisibleHeldProviderResults({
|
||||
items: [{ id: 'pr-1' }],
|
||||
value: 'fix',
|
||||
debouncedQuery: 'fi'
|
||||
})
|
||||
).toEqual([{ id: 'pr-1' }])
|
||||
})
|
||||
|
||||
it('shows provider results once the cleared field and debounce are both empty', () => {
|
||||
expect(
|
||||
getVisibleHeldProviderResults({
|
||||
items: [{ id: 'default-1' }],
|
||||
value: '',
|
||||
debouncedQuery: ''
|
||||
})
|
||||
).toEqual([{ id: 'default-1' }])
|
||||
})
|
||||
|
||||
it('uses stable cmdk values for typed-text actions', () => {
|
||||
expect(
|
||||
buildSmartWorkspaceSourceRows({
|
||||
mode: 'smart',
|
||||
value: 'refund-flow',
|
||||
branches: [],
|
||||
githubItems: [],
|
||||
gitlabItems: [],
|
||||
linearIssues: [],
|
||||
gitlabAvailable: false,
|
||||
linearAvailable: false,
|
||||
resultLimit: 12
|
||||
})[0]
|
||||
).toMatchObject({ kind: 'use-name', value: 'use-name', name: 'refund-flow' })
|
||||
|
||||
expect(
|
||||
buildSmartWorkspaceSourceRows({
|
||||
mode: 'branches',
|
||||
value: 'new-branch',
|
||||
branches: [],
|
||||
githubItems: [],
|
||||
gitlabItems: [],
|
||||
linearIssues: [],
|
||||
gitlabAvailable: false,
|
||||
linearAvailable: false,
|
||||
resultLimit: 12
|
||||
})[0]
|
||||
).toMatchObject({ kind: 'create-branch', value: 'create-branch', name: 'new-branch' })
|
||||
})
|
||||
|
||||
it('keeps matching empty-query branch results visible in Branch mode', () => {
|
||||
expect(
|
||||
getVisibleBranchResults({
|
||||
|
|
|
|||
|
|
@ -28,9 +28,14 @@ export function resolveSmartWorkspaceCommandValue({
|
|||
return currentValue
|
||||
}
|
||||
|
||||
// Why: freeze the arm while the live input is ahead of debounced search so the
|
||||
// highlight does not thrash to use-name / empty / first-row on every keystroke.
|
||||
if (isQueryStale) {
|
||||
if (rows.some((row) => row.value === currentValue)) {
|
||||
return currentValue
|
||||
}
|
||||
const typedTextRow = rows.find((row) => row.kind === 'use-name' || row.kind === 'create-branch')
|
||||
return typedTextRow?.value ?? ''
|
||||
return typedTextRow?.value ?? rows[0]?.value ?? ''
|
||||
}
|
||||
|
||||
if (sourceIntent === 'github') {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,29 @@ export function getBranchSearchRequest({
|
|||
return { repoId: selectedRepoId, query: trimmedQuery, limit }
|
||||
}
|
||||
|
||||
/**
|
||||
* Why: provider arrays lag the live input (200ms debounce). Keep them while the
|
||||
* user is still typing, but hide immediately when the field is cleared so prior
|
||||
* non-empty results cannot stay selectable until debounce catches up.
|
||||
*/
|
||||
export function getVisibleHeldProviderResults<T>({
|
||||
items,
|
||||
value,
|
||||
debouncedQuery
|
||||
}: {
|
||||
items: readonly T[]
|
||||
value: string
|
||||
debouncedQuery: string
|
||||
}): T[] {
|
||||
if (!isSmartWorkspaceSourceQueryWithinLimit(value)) {
|
||||
return []
|
||||
}
|
||||
if (value.trim() === '' && debouncedQuery.trim() !== '') {
|
||||
return []
|
||||
}
|
||||
return items.slice()
|
||||
}
|
||||
|
||||
export function getVisibleBranchResults({
|
||||
branches,
|
||||
mode,
|
||||
|
|
@ -93,16 +116,51 @@ export function getVisibleBranchResults({
|
|||
if (!isSmartWorkspaceSourceQueryWithinLimit(value)) {
|
||||
return []
|
||||
}
|
||||
const currentQuery = value.trim()
|
||||
if (mode !== 'branches' && mode !== 'smart') {
|
||||
return []
|
||||
}
|
||||
if (!selectedRepoId || resultRepoId !== selectedRepoId || resultQuery !== currentQuery) {
|
||||
if (!selectedRepoId || resultRepoId !== selectedRepoId || resultQuery === null) {
|
||||
return []
|
||||
}
|
||||
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 : []
|
||||
}
|
||||
if (!shouldHoldSourceResultsForQuery({ resultQuery, value: currentQuery })) {
|
||||
return []
|
||||
}
|
||||
return branches
|
||||
}
|
||||
|
||||
/** Max |live − settled| length while still treating a prefix as "still typing". */
|
||||
const SOURCE_RESULT_HOLD_MAX_DELTA = 4
|
||||
|
||||
/**
|
||||
* Why: prefix-only hold lets a settled "f" stick under "fix-unrelated-…" for the
|
||||
* whole next debounce. Cap the length delta so hold covers fast typing, not long
|
||||
* continuations of a short settled query.
|
||||
*/
|
||||
export function shouldHoldSourceResultsForQuery({
|
||||
resultQuery,
|
||||
value
|
||||
}: {
|
||||
resultQuery: string
|
||||
value: string
|
||||
}): boolean {
|
||||
const currentQueryKey = value.trim().toLowerCase()
|
||||
const resultQueryKey = resultQuery.trim().toLowerCase()
|
||||
if (resultQueryKey === currentQueryKey) {
|
||||
return true
|
||||
}
|
||||
if (!currentQueryKey.startsWith(resultQueryKey) && !resultQueryKey.startsWith(currentQueryKey)) {
|
||||
return false
|
||||
}
|
||||
return Math.abs(currentQueryKey.length - resultQueryKey.length) <= SOURCE_RESULT_HOLD_MAX_DELTA
|
||||
}
|
||||
|
||||
export function buildSmartWorkspaceSourceRows({
|
||||
branches,
|
||||
githubItems,
|
||||
|
|
@ -130,7 +188,8 @@ export function buildSmartWorkspaceSourceRows({
|
|||
const trimmed = value.trim()
|
||||
const nextRows: SmartWorkspaceSourceRow[] = []
|
||||
if (trimmed && mode === 'smart') {
|
||||
nextRows.push({ kind: 'use-name', value: `use-name-${trimmed}`, name: trimmed })
|
||||
// Why: stable cmdk value — embedding the query remounted the row every keystroke.
|
||||
nextRows.push({ kind: 'use-name', value: 'use-name', name: trimmed })
|
||||
}
|
||||
if (mode === 'text') {
|
||||
return nextRows
|
||||
|
|
@ -159,7 +218,7 @@ export function buildSmartWorkspaceSourceRows({
|
|||
(branch) => branch.refName === trimmed || branch.localBranchName === trimmed
|
||||
)
|
||||
if (trimmed && mode === 'branches' && !branchExactMatch) {
|
||||
nextRows.push({ kind: 'create-branch', value: `create-branch-${trimmed}`, name: trimmed })
|
||||
nextRows.push({ kind: 'create-branch', value: 'create-branch', name: trimmed })
|
||||
}
|
||||
nextRows.push(
|
||||
...branches.map((branch) => ({
|
||||
|
|
|
|||
Loading…
Reference in New Issue