+
+
+ {/* shrink-0 + max-w-full: the directory gives up all of its
+ width before the filename loses a character. */}
+
+ {filename}
+
+ {directory ? (
+ {directory}
+ ) : null}
+
+
)
})
diff --git a/src/renderer/src/components/file-path-cursor-tooltip.test.ts b/src/renderer/src/components/file-path-cursor-tooltip.test.ts
new file mode 100644
index 000000000..d33c6247b
--- /dev/null
+++ b/src/renderer/src/components/file-path-cursor-tooltip.test.ts
@@ -0,0 +1,55 @@
+// @vitest-environment happy-dom
+
+import { describe, expect, it } from 'vitest'
+import { cursorTooltipOffsets, splitTrailingSegment } from './file-path-cursor-tooltip'
+
+describe('cursorTooltipOffsets', () => {
+ const row = { bottom: 120, left: 400 }
+
+ it('places the tooltip under the cursor rather than the row', () => {
+ // Row-anchored placement would be align 0; the cursor is 64px into the row.
+ expect(cursorTooltipOffsets({ x: 464, y: 108 }, row)).toEqual({ align: 64, side: 6 })
+ })
+
+ it('tracks the cursor across the row', () => {
+ const left = cursorTooltipOffsets({ x: 410, y: 108 }, row)
+ const right = cursorTooltipOffsets({ x: 610, y: 108 }, row)
+
+ expect(right.align - left.align).toBe(200)
+ expect(right.side).toBe(left.side)
+ })
+
+ it('stays anchored to the cursor when the row moves under it', () => {
+ // The dropdown reflows while results stream in; re-measuring the row must
+ // keep the tooltip on the cursor, not drag it along with the row.
+ const before = cursorTooltipOffsets({ x: 464, y: 108 }, row)
+ const after = cursorTooltipOffsets({ x: 464, y: 108 }, { bottom: 148, left: 576 })
+
+ expect(row.left + before.align).toBe(576 + after.align)
+ expect(row.bottom + before.side).toBe(148 + after.side)
+ })
+})
+
+describe('splitTrailingSegment', () => {
+ it('keeps the separator on the directory', () => {
+ expect(splitTrailingSegment('app/src/SecondaryNav.tsx')).toEqual({
+ directory: 'app/src/',
+ filename: 'SecondaryNav.tsx'
+ })
+ })
+
+ it('does not duplicate the root separator', () => {
+ expect(splitTrailingSegment('/foo')).toEqual({ directory: '/', filename: 'foo' })
+ })
+
+ it('preserves Windows separators', () => {
+ expect(splitTrailingSegment('C:\\repo\\src\\a.ts')).toEqual({
+ directory: 'C:\\repo\\src\\',
+ filename: 'a.ts'
+ })
+ })
+
+ it('returns no directory for a bare filename', () => {
+ expect(splitTrailingSegment('README.md')).toEqual({ directory: '', filename: 'README.md' })
+ })
+})
diff --git a/src/renderer/src/components/file-path-cursor-tooltip.tsx b/src/renderer/src/components/file-path-cursor-tooltip.tsx
new file mode 100644
index 000000000..d156d3a93
--- /dev/null
+++ b/src/renderer/src/components/file-path-cursor-tooltip.tsx
@@ -0,0 +1,100 @@
+import React from 'react'
+import { Slot } from 'radix-ui'
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
+
+// Clears the pointer without putting the label under the cursor's own hotspot.
+const CURSOR_TOOLTIP_GAP = 18
+
+/**
+ * Radix positions the tooltip against the trigger, so a cursor position has to
+ * be restated as offsets from the trigger's bottom-left corner to land under
+ * the pointer.
+ */
+export function cursorTooltipOffsets(
+ pointer: { x: number; y: number },
+ trigger: { bottom: number; left: number }
+): { align: number; side: number } {
+ return {
+ align: pointer.x - trigger.left,
+ side: pointer.y + CURSOR_TOOLTIP_GAP - trigger.bottom
+ }
+}
+
+/**
+ * Splits a path for filename-first display, keeping the separator attached to
+ * the directory so `/foo` renders `foo` + `/` and Windows paths keep `\`.
+ */
+export function splitTrailingSegment(path: string): { directory: string; filename: string } {
+ const separatorIndex = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
+
+ return separatorIndex === -1
+ ? { directory: '', filename: path }
+ : { directory: path.slice(0, separatorIndex + 1), filename: path.slice(separatorIndex + 1) }
+}
+
+/**
+ * Wraps a single result row so its full path appears in a system-styled tooltip
+ * anchored under the cursor. The child is the trigger; it receives a ref and a
+ * pointer-move handler.
+ */
+export function FilePathCursorTooltip({
+ children,
+ path
+}: {
+ children: React.ReactNode
+ path: string
+}): React.JSX.Element {
+ const triggerRef = React.useRef