From 55e731ade968d72959b2dc636f22a803983f88c1 Mon Sep 17 00:00:00 2001 From: guettlibot Date: Sun, 7 Jun 2026 00:56:35 +0000 Subject: [PATCH] fix(search): sort search results by received date descending Previously searchEmails and searchEmailsGlobal used FTS5's `ORDER BY rank` (relevance ordering), making results appear in a seemingly random order rather than newest-first like the normal email list view. After merging the FTS results with note-search results the combined list was also never re-sorted. Change the SQL to ORDER BY e.received_at DESC and add an in-memory sort on the merged list so the final result is always newest-first, consistent with the rest of the app. Closes #509 Co-Authored-By: Claude Sonnet 4.6 --- .../repositories/email_repository_impl.dart | 8 ++- test/unit/email_repository_impl_test.dart | 64 +++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/lib/data/repositories/email_repository_impl.dart b/lib/data/repositories/email_repository_impl.dart index 911c1a9..fd10524 100644 --- a/lib/data/repositories/email_repository_impl.dart +++ b/lib/data/repositories/email_repository_impl.dart @@ -2922,9 +2922,9 @@ class EmailRepositoryImpl implements EmailRepository { final sql = accountId != null ? 'SELECT e.* FROM email_fts f JOIN emails e ON e.rowid = f.rowid' - ' WHERE email_fts MATCH ? AND e.account_id = ? ORDER BY rank LIMIT 50' + ' WHERE email_fts MATCH ? AND e.account_id = ? ORDER BY e.received_at DESC LIMIT 50' : 'SELECT e.* FROM email_fts f JOIN emails e ON e.rowid = f.rowid' - ' WHERE email_fts MATCH ? ORDER BY rank LIMIT 50'; + ' WHERE email_fts MATCH ? ORDER BY e.received_at DESC LIMIT 50'; final variables = accountId != null ? [Variable(ftsQuery), Variable(accountId)] : [Variable(ftsQuery)]; @@ -2942,6 +2942,7 @@ class EmailRepositoryImpl implements EmailRepository { for (final e in [...emailRows.map(_toModel), ...noteRows]) { if (seen.add(e.id)) merged.add(e); } + merged.sort((a, b) => b.receivedAt.compareTo(a.receivedAt)); return merged; } @@ -3113,7 +3114,7 @@ class EmailRepositoryImpl implements EmailRepository { const sql = 'SELECT e.* FROM email_fts f JOIN emails e ON e.rowid = f.rowid' ' WHERE email_fts MATCH ? AND e.account_id = ? AND e.mailbox_path = ?' - ' ORDER BY rank LIMIT 50'; + ' ORDER BY e.received_at DESC LIMIT 50'; final variables = [ Variable(ftsQuery), Variable(accountId), @@ -3134,6 +3135,7 @@ class EmailRepositoryImpl implements EmailRepository { for (final e in [...emailRows.map(_toModel), ...noteRows]) { if (seen.add(e.id)) merged.add(e); } + merged.sort((a, b) => b.receivedAt.compareTo(a.receivedAt)); return merged; } diff --git a/test/unit/email_repository_impl_test.dart b/test/unit/email_repository_impl_test.dart index ff24382..b93f7be 100644 --- a/test/unit/email_repository_impl_test.dart +++ b/test/unit/email_repository_impl_test.dart @@ -576,6 +576,70 @@ void main() { expect(results.first.mailboxPath, 'INBOX'); }); + test('searchEmailsGlobal returns results sorted by receivedAt descending', + () async { + final r = _makeRepos(); + await r.accounts.addAccount(_account, 'pw'); + + await r.db.into(r.db.emails).insert( + EmailsCompanion.insert( + id: 'acc-1:1', + accountId: 'acc-1', + mailboxPath: 'INBOX', + uid: 1, + subject: const Value('Older report'), + receivedAt: DateTime(2024, 1), + ), + ); + await r.db.into(r.db.emails).insert( + EmailsCompanion.insert( + id: 'acc-1:2', + accountId: 'acc-1', + mailboxPath: 'INBOX', + uid: 2, + subject: const Value('Newer report'), + receivedAt: DateTime(2024, 6), + ), + ); + + final results = await r.emails.searchEmailsGlobal(null, 'report'); + expect(results, hasLength(2)); + expect(results[0].subject, 'Newer report'); + expect(results[1].subject, 'Older report'); + }); + + test('searchEmails returns results sorted by receivedAt descending', + () async { + final r = _makeRepos(); + await r.accounts.addAccount(_account, 'pw'); + + await r.db.into(r.db.emails).insert( + EmailsCompanion.insert( + id: 'acc-1:1', + accountId: 'acc-1', + mailboxPath: 'INBOX', + uid: 1, + subject: const Value('Older meeting'), + receivedAt: DateTime(2024, 1), + ), + ); + await r.db.into(r.db.emails).insert( + EmailsCompanion.insert( + id: 'acc-1:2', + accountId: 'acc-1', + mailboxPath: 'INBOX', + uid: 2, + subject: const Value('Newer meeting'), + receivedAt: DateTime(2024, 6), + ), + ); + + final results = await r.emails.searchEmails('acc-1', 'INBOX', 'meeting'); + expect(results, hasLength(2)); + expect(results[0].subject, 'Newer meeting'); + expect(results[1].subject, 'Older meeting'); + }); + test( 'searchAddresses returns results sorted by most recently used', () async {