diff --git a/src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx b/src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx index 8a168bcb684..3bc66004a01 100644 --- a/src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx +++ b/src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx @@ -79,6 +79,18 @@ function press(key: string): void { }) } +function rowTexts(): string[] { + return [...container.querySelectorAll('[role="option"]')].map((row) => row.textContent ?? '') +} + +function seedBrowserHistory(url: string, title: string): void { + useAppStore.setState({ + browserUrlHistory: [ + { url, normalizedUrl: url.replace(/\/$/, ''), title, lastVisitedAt: 1, visitCount: 1 } + ] + }) +} + beforeEach(() => { fileListMock.current = { files: [], loading: false, loadError: null } tabSearchMock.calls = [] @@ -134,6 +146,83 @@ describe('TabBarCreateEntry search behavior', () => { ) }) + it.each([false, true])( + 'keeps a typed host and its pages above unrelated files (visited root: %s)', + (visitedRoot) => { + fileListMock.current.files = [ + 'mobile/rpc-foundation/goldens/matrix-transport.capability-probe-status.get-1.json', + 'mobile/rpc-foundation/goldens/matrix-files.terminal-path-tap-files.open-1.json', + 'mobile/rpc-foundation/goldens/matrix-files.terminal-path-tap-files.resolveterminalpath-1.json' + ] + const url = visitedRoot ? 'https://linear.app/' : 'https://linear.app/acme/issues' + seedBrowserHistory(url, 'Linear') + const onOpenEntry = vi.fn().mockResolvedValue(undefined) + renderEntry({ onOpenEntry }) + setQuery('linear.app') + + const rows = rowTexts() + const pageIndex = rows.findIndex((row) => row.includes('Open page')) + expect(pageIndex).toBeGreaterThanOrEqual(0) + expect(rows.findIndex((row) => row.includes('Open file'))).toBeGreaterThan(pageIndex) + expect(container.querySelector('[aria-selected="true"]')?.textContent).toContain('linear.app') + submit() + expect(onOpenEntry).toHaveBeenCalledWith( + expect.objectContaining({ + classification: visitedRoot + ? { kind: 'explicit-url', url } + : { kind: 'host-url', url: 'https://linear.app/' } + }) + ) + } + ) + + it('still opens a visited host above a filename that contains it', () => { + fileListMock.current.files = ['config/localhost.ts'] + seedBrowserHistory('http://localhost/', 'Local') + const onOpenEntry = vi.fn().mockResolvedValue(undefined) + renderEntry({ onOpenEntry }) + setQuery('localhost') + + submit() + expect(onOpenEntry).toHaveBeenCalledWith( + expect.objectContaining({ + classification: { kind: 'explicit-url', url: 'http://localhost/' } + }) + ) + }) + + // A code-host page title carries the file path, so it matches path queries too. + it.each([false, true])( + 'keeps a matching page below local rows for a path query (typed: %s)', + (typed) => { + fileListMock.current.files = [ + 'src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx', + 'src/renderer/src/components/tab-bar/TabBarCreateEntryRow.tsx' + ] + seedBrowserHistory( + 'https://github.com/acme/app/blob/main/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx', + 'app/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx at main · acme/app' + ) + const onOpenEntry = vi.fn().mockResolvedValue(undefined) + renderEntry({ onOpenEntry }) + const query = 'components/tab-bar/TabBarCreate' + if (typed) { + for (let length = 1; length <= query.length; length += 1) { + setQuery(query.slice(0, length)) + } + } else { + setQuery(query) + } + + const rows = rowTexts() + const pageIndex = rows.findIndex((row) => row.includes('Open page')) + expect(pageIndex).toBeGreaterThan(rows.findLastIndex((row) => row.includes('Open file'))) + submit() + expect(onOpenEntry).toHaveBeenCalledOnce() + expect(onOpenEntry.mock.calls[0]?.[0].classification.kind).not.toBe('explicit-url') + } + ) + it.each(['a', 'ab', 'asciinema'])('waits for an exact filename before arming %s', (query) => { fileListMock.current = { files: [], loading: true, loadError: null } const onOpenEntry = vi.fn().mockResolvedValue(undefined) diff --git a/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx b/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx index aaf0c74210c..ac80ca0699c 100644 --- a/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx +++ b/src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx @@ -16,7 +16,7 @@ import { resultOptionDomId } from './TabBarCreateEntryRow' import { dropFileEntriesCoveredByTabResults } from './open-tab-entry-dedupe' -import { insertHistoryRowsBelowFileMatches } from './tab-create-entry-history-placement' +import { placeHistoryRowsInEntryOptions } from './tab-create-entry-history-placement' import { useOmniboxBrowserHistory } from './use-omnibox-browser-history' import { useTabEntryMenuReturnFocus } from './use-tab-entry-menu-return-focus' import { activateOpenTabSearchResult } from './open-tab-selection-routing' @@ -167,7 +167,7 @@ function TabBarCreateEntrySession({ kind: 'agent' as const, option })), - ...insertHistoryRowsBelowFileMatches(options, historyRows) + ...placeHistoryRowsInEntryOptions(options, historyRows) ] const { activeSelectedIndex, selectedActiveOption } = useNetworkSafeTabEntrySelection({ activeOptions, diff --git a/src/renderer/src/components/tab-bar/open-tab-entry-dedupe.ts b/src/renderer/src/components/tab-bar/open-tab-entry-dedupe.ts index 891cb93353c..a6f4a01a165 100644 --- a/src/renderer/src/components/tab-bar/open-tab-entry-dedupe.ts +++ b/src/renderer/src/components/tab-bar/open-tab-entry-dedupe.ts @@ -63,10 +63,10 @@ export function dropHistoryRowsCoveredByBrowserPages( // The history row carries a real page title, so it beats the bare typed-URL row // it duplicates — mirroring the address bar's synthetic-row fold. -export function dropUrlEntriesCoveredByHistoryRows( - options: readonly TabEntryOption[], +export function dropUrlEntriesCoveredByHistoryRows