From 96c40921388c35760f294cadd255e66beb4f1518 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 03:45:24 +0000 Subject: [PATCH] fix: ensure secondary sort by id ascending when sorting by createdAt descending When events share the same createdAt timestamp, the tiebreaker sort by id must be ascending to produce a stable, deterministic order. Fixed several locations that either had no secondary sort, used descending id sort, or used .reversed() which incorrectly flipped both sort directions. https://claude.ai/code/session_01RvuyPf1x9wLf2DCgsSXLGz --- .../DiscoverMarketplaceFeedFilter.kt | 2 +- .../loggedIn/notifications/CardFeedContentState.kt | 12 ++++-------- .../screen/loggedIn/notifications/OpenPollsState.kt | 2 +- .../amethyst/commons/search/SearchResultFilter.kt | 2 +- .../amethyst/commons/search/SearchResultSorter.kt | 6 +++--- .../commons/ui/notifications/CardFeedState.kt | 2 +- .../vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt | 2 +- .../amethyst/desktop/ui/UserProfileScreen.kt | 4 ++-- 8 files changed, 14 insertions(+), 18 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt index ebf796682..c83f7480d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt @@ -75,5 +75,5 @@ open class DiscoverMarketplaceFeedFilter( } } - override fun sort(items: Set): List = items.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() + override fun sort(items: Set): List = items.sortedWith(compareByDescending { it.createdAt() }.thenBy { it.idHex }) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt index ad7de41b2..5d8f6d682 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt @@ -262,8 +262,7 @@ class CardFeedContentState( val sortedList = singleList .get(string) - ?.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) - ?.reversed() + ?.sortedWith(compareByDescending { it.createdAt() }.thenBy { it.idHex }) sortedList?.chunked(30)?.map { chunk -> MultiSetCard( @@ -293,8 +292,7 @@ class CardFeedContentState( ZapUserSetCard( user.key, zaps - .sortedWith(compareBy({ it.createdAt() }, { it.idHex() })) - .reversed() + .sortedWith(compareByDescending { it.createdAt() }.thenBy { it.idHex() }) .toImmutableList(), ) } @@ -318,8 +316,7 @@ class CardFeedContentState( } return (multiCards + textNoteCards + userZaps) - .sortedWith(compareBy({ it.createdAt() }, { it.id() })) - .reversed() + .sortedWith(compareByDescending { it.createdAt() }.thenBy { it.id() }) } private fun updateFeed(notes: ImmutableList) { @@ -383,8 +380,7 @@ class CardFeedContentState( val updatedCards = (oldNotesState.feed.value.list + newCards) .distinctBy { it.id() } - .sortedWith(compareBy({ it.createdAt() }, { it.id() })) - .reversed() + .sortedWith(compareByDescending { it.createdAt() }.thenBy { it.id() }) .take(localFilter.limit()) .toImmutableList() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/OpenPollsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/OpenPollsState.kt index 83b626a13..ede972e7c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/OpenPollsState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/OpenPollsState.kt @@ -80,6 +80,6 @@ class OpenPollsState( false } } - }.sortedByDescending { it.createdAt() } + }.sortedWith(compareByDescending { it.createdAt() }.thenBy { it.idHex }) } } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultFilter.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultFilter.kt index 6cff2d8cf..c0a73021e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultFilter.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultFilter.kt @@ -53,7 +53,7 @@ object SearchResultFilter { } // Sort by createdAt descending - return result.sortedByDescending { it.createdAt } + return result.sortedWith(compareByDescending { it.createdAt }.thenBy { it.id }) } fun isReply(event: Event): Boolean = event.kind == 1 && event.tags.any { it.size >= 2 && it[0] == "e" } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultSorter.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultSorter.kt index bc1ea8177..04e92ebec 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultSorter.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/search/SearchResultSorter.kt @@ -33,16 +33,16 @@ object SearchResultSorter { ): List = when (order) { SearchSortOrder.NEWEST -> { - events.sortedByDescending { it.createdAt } + events.sortedWith(compareByDescending { it.createdAt }.thenBy { it.id }) } SearchSortOrder.OLDEST -> { - events.sortedBy { it.createdAt } + events.sortedWith(compareBy { it.createdAt }.thenBy { it.id }) } SearchSortOrder.RELEVANCE -> { if (searchText.isBlank()) { - events.sortedByDescending { it.createdAt } + events.sortedWith(compareByDescending { it.createdAt }.thenBy { it.id }) } else { events.sortedByDescending { scoreEvent(it, searchText) } } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/notifications/CardFeedState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/notifications/CardFeedState.kt index b2424cd7f..cc46f38a2 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/notifications/CardFeedState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/notifications/CardFeedState.kt @@ -75,4 +75,4 @@ sealed class CardFeedState { */ val DefaultCardComparator: Comparator = compareByDescending { it.createdAt() } - .thenByDescending { it.id() } + .thenBy { it.id() } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt index 42f3ad82a..7ff4b2b51 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt @@ -196,7 +196,7 @@ fun ReadsScreen( remember { EventCollectionState( getId = { it.id }, - sortComparator = compareByDescending { it.publishedAt() ?: it.createdAt }, + sortComparator = compareByDescending { it.publishedAt() ?: it.createdAt }.thenBy { it.id }, maxSize = 100, scope = scope, ) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt index 914f6fcbd..1b61d4772 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt @@ -829,7 +829,7 @@ fun UserProfileScreen( } } else { items( - articleEvents.sortedByDescending { it.publishedAt() ?: it.createdAt }, + articleEvents.sortedWith(compareByDescending { it.publishedAt() ?: it.createdAt }.thenBy { it.id }), key = { "art-${it.id}" }, ) { article -> LongFormCard( @@ -871,7 +871,7 @@ fun UserProfileScreen( } } else { items( - highlightEvents.sortedByDescending { it.createdAt }, + highlightEvents.sortedWith(compareByDescending { it.createdAt }.thenBy { it.id }), key = { "hl-${it.id}" }, ) { highlight -> PublishedHighlightCard(