fix: stable sort in ShortsFeedFilter to avoid TimSort contract crash

A newer AddressableEvent (e.g. VideoVerticalEvent) arriving on a relay
thread can swap a Note's event mid-sort, changing the createdAt value
the comparator returned moments earlier. TimSort then trips
"Comparison method violates its general contract!" and the feed
refresh crashes.

Snapshot createdAt once per note before sorting via a new
sortedByDefaultFeedOrder() helper.
This commit is contained in:
Claude
2026-05-19 17:57:15 +00:00
parent ed686b71ed
commit df87557ef3
2 changed files with 11 additions and 2 deletions
@@ -32,3 +32,12 @@ val DefaultFeedOrderEvent: Comparator<Event> =
val DefaultFeedOrderCard: Comparator<Card> =
compareByDescending<Card> { it.createdAt() }.thenBy { it.id() }
// Snapshots createdAt once per note so the comparator stays consistent even if
// another thread swaps a Note's event mid-sort (e.g. a newer AddressableEvent
// arriving from a relay). Avoids TimSort's "Comparison method violates its
// general contract!" IllegalArgumentException.
fun Iterable<Note>.sortedByDefaultFeedOrder(): List<Note> =
map { it to (it.createdAt() ?: 0L) }
.sortedWith(compareByDescending<Pair<Note, Long>> { it.second }.thenBy { it.first.idHex })
.map { it.first }
@@ -28,8 +28,8 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.TopFilter
import com.vitorpamplona.amethyst.model.filterIntoSet
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
import com.vitorpamplona.amethyst.ui.dal.sortedByDefaultFeedOrder
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal.SupportedContent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.SUPPORTED_VIDEO_FEED_MIME_TYPES_SET
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
@@ -113,5 +113,5 @@ class ShortsFeedFilter(
(params.isHiddenList || account.isAcceptable(note))
}
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
override fun sort(items: Set<Note>): List<Note> = items.sortedByDefaultFeedOrder()
}