diff --git a/src/main/ai-vault-search/session-search-hit-ranking.test.ts b/src/main/ai-vault-search/session-search-hit-ranking.test.ts index 54919bace0f..63df3c0e77b 100644 --- a/src/main/ai-vault-search/session-search-hit-ranking.test.ts +++ b/src/main/ai-vault-search/session-search-hit-ranking.test.ts @@ -39,6 +39,15 @@ describe('order', () => { expect(rankSessionHits(sessions, scores, 'newest').map((e) => e.session.id)).toEqual([2, 1]) }) + it('hands a relevance tie to the newer session before falling back to id', () => { + const sessions = [ + session(1, { updated_at: '2026-09-01T00:00:00.000Z' }), + session(2, { updated_at: '2026-09-09T00:00:00.000Z' }) + ] + const scores = matches(match(1, 5), match(2, 5)) + expect(rankSessionHits(sessions, scores, 'relevance').map((e) => e.session.id)).toEqual([2, 1]) + }) + it.each(['relevance', 'newest'] as const)( 'breaks a %s tie by session, whatever order retrieval handed them over in', (sort) => { diff --git a/src/main/ai-vault-search/session-search-hit-ranking.ts b/src/main/ai-vault-search/session-search-hit-ranking.ts index 364858ea650..2172113074a 100644 --- a/src/main/ai-vault-search/session-search-hit-ranking.ts +++ b/src/main/ai-vault-search/session-search-hit-ranking.ts @@ -65,11 +65,12 @@ export function rankSessionHits( ) // Why a total order and not just the key: a cursor is an offset into this // list, so two entries that tie must not be free to swap between pages. + // Newer first among equal scores, so relevance never hands ties to whichever id is lower. scored.sort( (left, right) => - (sort === 'newest' - ? (right.session.updated_at ?? '').localeCompare(left.session.updated_at ?? '') - : right.score - left.score) || left.session.id - right.session.id + (sort === 'newest' ? 0 : right.score - left.score) || + (right.session.updated_at ?? '').localeCompare(left.session.updated_at ?? '') || + left.session.id - right.session.id ) return scored } diff --git a/src/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsx b/src/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsx index 7eed10ad14b..92424db0506 100644 --- a/src/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsx +++ b/src/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsx @@ -3,7 +3,7 @@ import { cleanup, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { afterEach, beforeEach, expect, it, vi } from 'vitest' import type { AiVaultSession } from '../../../../shared/ai-vault-types' -import type { AiVaultSessionGroup } from './ai-vault-session-filters' +import type { AiVaultSessionListGroup } from './ai-vault-session-filters' const mockState: { settings: { aiVaultSearch?: { enabled: boolean } } @@ -76,8 +76,9 @@ vi.mock('./ai-vault-session-delete-action', () => ({ // The virtualizer measures a zero-height viewport under happy-dom; the rows it would // choose are exactly the grouped sessions, so render those instead. vi.mock('./AiVaultSessionVirtualList', () => ({ - AiVaultSessionVirtualList: ({ groups }: { groups: readonly AiVaultSessionGroup[] }) => ( -