diff --git a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlanner.kt b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlanner.kt index 469c4ea743..60438baf4a 100644 --- a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlanner.kt +++ b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlanner.kt @@ -13,6 +13,10 @@ package com.flipcash.shared.chat.reactions * reaction state, so refreshing it again would be redundant. * * Both are pure: callers own the "already refreshed" bookkeeping and pass in what's still needed. + * + * Both drop ids below 1. A pending or failed send is stored as `-(now)`, has no reactions to fetch, + * and fails the `MessageId` rule (>= 1) for the whole batch it rides in. Its real id arrives with + * the server echo and pages in as a new id. */ object ReactionRefreshPlanner { @@ -30,6 +34,7 @@ object ReactionRefreshPlanner { ): List = newestFirstIds .asSequence() + .filter { it >= 1 } .take(windowSize) .filterNot { it in alreadyRefreshed } .toList() @@ -47,7 +52,7 @@ object ReactionRefreshPlanner { chunkSize: Int = DEFAULT_CHUNK_SIZE, ): List> { if (sourcedFromServer) return emptyList() - val remaining = pageIds.filterNot { it in alreadyRefreshed } + val remaining = pageIds.filter { it >= 1 && it !in alreadyRefreshed } if (remaining.isEmpty()) return emptyList() return remaining.chunked(chunkSize) } diff --git a/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlannerTest.kt b/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlannerTest.kt index afecbb764f..be5b178b34 100644 --- a/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlannerTest.kt +++ b/apps/flipcash/shared/chat/src/test/kotlin/com/flipcash/shared/chat/reactions/ReactionRefreshPlannerTest.kt @@ -60,4 +60,20 @@ class ReactionRefreshPlannerTest { val chunks = ReactionRefreshPlanner.forLoadedPage(emptyList(), sourcedFromServer = false) assertEquals(emptyList>(), chunks) } + + // A pending or failed send is stored as -(now) and sorts among the newest rows. The + // MessageId rule is >= 1, so one in a batch fails the whole request. + + @Test + fun `initial window skips unsent ids and still fills the window`() { + val unsent = -1_700_000_000_000L + val window = ReactionRefreshPlanner.initialWindow(listOf(unsent, 5L, 4L, 3L), windowSize = 2) + assertEquals(listOf(5L, 4L), window) + } + + @Test + fun `loaded page of only an unsent id yields no chunks`() { + val chunks = ReactionRefreshPlanner.forLoadedPage(listOf(-1_700_000_000_000L), sourcedFromServer = false) + assertEquals(emptyList>(), chunks) + } }