diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b90bc77a..dc40e57d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the web process being capped at a ~4GiB heap regardless of how much memory the container has, which caused multi-second garbage collection pauses on larger deployments. [#1569](https://github.com/sourcebot-dev/sourcebot/pull/1569) - Upgraded `@sentry/*` to `^10.70.0`, fixing memory leaks where spans retained request data indefinitely. [#1572](https://github.com/sourcebot-dev/sourcebot/pull/1572) +- Fixed code search result links occasionally getting stuck during navigation and restored Cmd/Ctrl-click to open matches in preview. [#1574](https://github.com/sourcebot-dev/sourcebot/pull/1574) ## [5.1.6] - 2026-08-10 diff --git a/packages/web/src/app/(app)/components/pathHeader.tsx b/packages/web/src/app/(app)/components/pathHeader.tsx index 924a35541..bd89253d1 100644 --- a/packages/web/src/app/(app)/components/pathHeader.tsx +++ b/packages/web/src/app/(app)/components/pathHeader.tsx @@ -230,6 +230,7 @@ export const PathHeader = ({ @ {hiddenSegments.map((segment) => ( )} ({ + default: ({ children, href, prefetch, ...props }: React.AnchorHTMLAttributes & { prefetch?: boolean }) => ( + + {children} + + ), +})); + +vi.mock('@/app/(app)/components/lightweightCodeHighlighter', () => ({ + LightweightCodeHighlighter: ({ children }: { children: React.ReactNode }) => <>{children}, +})); + +const file = { + fileName: { + text: 'src/index.ts', + matchRanges: [], + }, + webUrl: '', + repository: 'github.com/sourcebot-dev/sourcebot', + repositoryId: 1, + language: 'TypeScript', + branches: ['main'], + chunks: [], +}; + +const match = { + content: 'const result = true;', + contentStart: { + byteOffset: 0, + lineNumber: 10, + column: 1, + }, + matchRanges: [{ + start: { + byteOffset: 6, + lineNumber: 10, + column: 7, + }, + end: { + byteOffset: 12, + lineNumber: 10, + column: 13, + }, + }], +}; + +afterEach(() => { + cleanup(); +}); + +describe('FileMatch', () => { + it('disables prefetching and preserves ordinary link clicks', () => { + const onOpenPreview = vi.fn(); + render(); + + const link = screen.getByRole('link'); + expect(link.getAttribute('data-prefetch')).toBe('false'); + + fireEvent.click(link); + expect(onOpenPreview).not.toHaveBeenCalled(); + }); + + it.each([ + ['Cmd', { metaKey: true }], + ['Ctrl', { ctrlKey: true }], + ])('opens the preview and cancels navigation for %s-click', (_modifier, eventInit) => { + const onOpenPreview = vi.fn(); + render(); + + const link = screen.getByRole('link'); + const clickEvent = createEvent.click(link, eventInit); + fireEvent(link, clickEvent); + + expect(clickEvent.defaultPrevented).toBe(true); + expect(onOpenPreview).toHaveBeenCalledOnce(); + }); + + it('supports modifier-plus-Enter for keyboard users', () => { + const onOpenPreview = vi.fn(); + render(); + + const link = screen.getByRole('link'); + const keyDownEvent = createEvent.keyDown(link, { key: 'Enter', metaKey: true }); + fireEvent(link, keyDownEvent); + + expect(keyDownEvent.defaultPrevented).toBe(true); + expect(onOpenPreview).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatch.tsx b/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatch.tsx index eba49587f..a53ffb2c8 100644 --- a/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatch.tsx +++ b/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatch.tsx @@ -9,11 +9,13 @@ import { getBrowsePath } from "@/app/(app)/browse/hooks/utils"; interface FileMatchProps { match: SearchResultChunk; file: SearchResultFile; + onOpenPreview: () => void; } export const FileMatch = ({ match, file, + onOpenPreview, }: FileMatchProps) => { // If it's just the title, don't show a code preview if (match.matchRanges.length === 0) { @@ -24,6 +26,7 @@ export const FileMatch = ({ { + if (!event.metaKey && !event.ctrlKey) { + return; + } + + event.preventDefault(); + onOpenPreview(); + }} + onKeyDown={(event) => { + if (event.key !== 'Enter' || (!event.metaKey && !event.ctrlKey)) { + return; + } + + event.preventDefault(); + onOpenPreview(); + }} title="open file: click, open file preview: cmd/ctrl + click" > ); -} \ No newline at end of file +} diff --git a/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatchContainer.tsx b/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatchContainer.tsx index 1c5bf60dc..e134dd4e6 100644 --- a/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatchContainer.tsx +++ b/packages/web/src/app/(app)/search/components/searchResultsPanel/fileMatchContainer.tsx @@ -123,6 +123,12 @@ export const FileMatchContainer = ({ { + const matchIndex = matches.slice(0, index).reduce((acc, previousMatch) => { + return acc + previousMatch.matchRanges.length; + }, 0); + onOpenFilePreview(matchIndex); + }} /> {(index !== matches.length - 1 || isMoreContentButtonVisible) && ( @@ -153,4 +159,4 @@ export const FileMatchContainer = ({ )} ); -} \ No newline at end of file +}