diff --git a/desktop/src/components/chat/ConversationNavigator.test.tsx b/desktop/src/components/chat/ConversationNavigator.test.tsx index 56534d5d..2cf1cb9a 100644 --- a/desktop/src/components/chat/ConversationNavigator.test.tsx +++ b/desktop/src/components/chat/ConversationNavigator.test.tsx @@ -84,6 +84,7 @@ describe('ConversationNavigator', () => { it('renders ordered role markers and identifies the active target', () => { render( { expect(markers.map((marker) => marker.getAttribute('data-role'))).toEqual(['user', 'assistant']) expect(markers[0]?.getAttribute('aria-current')).toBeNull() expect(markers[1]?.getAttribute('aria-current')).toBe('location') + + const markerBars = markers.map((marker) => marker.querySelector('[aria-hidden="true"]')) + expect(screen.getByTestId('conversation-navigator').getAttribute('data-mode')).toBe('full') + expect(markerBars.every((bar) => bar?.className.includes('w-3'))).toBe(true) + expect(markerBars.every((bar) => bar?.className.includes('group-hover:w-5'))).toBe(true) + expect(markerBars.every((bar) => bar?.className.includes('group-focus-visible:w-5'))).toBe(true) + expect(markerBars[1]?.className).toContain('bg-[var(--color-brand)]') + expect(markerBars[1]?.className.split(/\s+/)).not.toContain('w-5') + }) + + it('uses equal shorter marker geometry in compact mode', () => { + render( + , + ) + + const markers = screen.getAllByRole('button') + const markerBars = markers.map((marker) => marker.querySelector('[aria-hidden="true"]')) + expect(screen.getByTestId('conversation-navigator').getAttribute('data-mode')).toBe('compact') + expect(markerBars.every((bar) => bar?.className.includes('w-2.5'))).toBe(true) + expect(markerBars.every((bar) => bar?.className.includes('group-hover:w-4'))).toBe(true) + expect(markerBars.every((bar) => bar?.className.includes('motion-reduce:transition-none'))).toBe(true) + }) + + it('uses an edge-sized lane when the transcript becomes narrow', () => { + render( + , + ) + + const markers = screen.getAllByRole('button') + const markerBars = markers.map((marker) => marker.querySelector('[aria-hidden="true"]')) + expect(screen.getByTestId('conversation-navigator').getAttribute('data-mode')).toBe('edge') + expect(markerBars.every((bar) => bar?.className.includes('w-1.5'))).toBe(true) + expect(markerBars.every((bar) => bar?.className.includes('group-hover:w-3'))).toBe(true) }) it('shows the preview on hover or focus and navigates on click', () => { @@ -111,6 +161,7 @@ describe('ConversationNavigator', () => { } render( = { + full: { + position: 'left-2', + lane: 'w-10', + button: 'w-10 pl-1.5', + marker: 'w-3 group-hover:w-5 group-focus-visible:w-5', + }, + compact: { + position: 'left-1', + lane: 'w-7', + button: 'w-7 pl-1', + marker: 'w-2.5 group-hover:w-4 group-focus-visible:w-4', + }, + edge: { + position: 'left-0', + lane: 'w-5', + button: 'w-5 pl-0.5', + marker: 'w-1.5 group-hover:w-3 group-focus-visible:w-3', + }, +} + function normalizePreview(content: string) { const normalized = content.slice(0, 2_000) .replace(/\[([^\]]+)]\([^)]+\)/g, '$1') @@ -51,10 +79,12 @@ export function buildConversationNavigationItems( } export function ConversationNavigator({ + mode, items, activeItemId, onNavigate, }: { + mode: ConversationNavigationMode items: ConversationNavigationItem[] activeItemId: string | null onNavigate: (item: ConversationNavigationItem) => void @@ -64,6 +94,7 @@ export function ConversationNavigator({ const [previewPosition, setPreviewPosition] = useState({ left: 0, top: 0 }) const markerRefs = useRef(new Map()) const previewItem = items.find((item) => item.id === previewItemId) ?? null + const modeStyles = NAVIGATION_MODE_STYLES[mode] const openPreview = (itemId: string, marker: HTMLButtonElement) => { const rect = marker.getBoundingClientRect() @@ -82,10 +113,11 @@ export function ConversationNavigator({ return (