From 5976c4628d84bc74ff3cc90af97cd0340c541b7b Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Wed, 14 Oct 2026 13:03:37 -0700 Subject: [PATCH] fix(new-tab): Enter on a visited site's name opens the site, not a file (#28804) * Rank browser history above fuzzy file matches in the omnibox Scattered-character fuzzy file matches are weak evidence, so a remembered page should appear above them. Exact and literal file matches and typed URLs still rank above history. Adds a test for typing a host like linear.app, which should rank the page above unrelated fuzzy files. * Anchor omnibox history on the typed URL instead of demoting fuzzy files The real bug: after visiting https://linear.app/, typing `linear.app` made Enter open an unrelated file. The history row removes the typed-URL row it duplicates, but was then placed below every file match, so the page lost the top slot the URL row had. History now sits with the typed URL row (in its slot when it replaced it). Queries with no typed URL keep the old rule: below every file match. This replaces the previous commit's rule of ranking history above "fuzzy" files. That label covers every non-exact match for queries with a slash, a dot suffix or a space, so a code-host page whose title holds the file path took Enter away from local rows for path queries (or left nothing selected when pasted). It also left `localhost` broken when a file name contained it. --- .../tab-bar/TabBarCreateEntry.search.test.tsx | 89 +++++++++++++++++ .../components/tab-bar/TabBarCreateEntry.tsx | 4 +- .../tab-bar/open-tab-entry-dedupe.ts | 6 +- ...tab-create-entry-history-placement.test.ts | 99 ++++++++++++------- .../tab-create-entry-history-placement.ts | 65 +++++++----- 5 files changed, 200 insertions(+), 63 deletions(-) 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