Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91d3404
refactor(chat): extract message sync into reusable component
AndyScherzinger Aug 2, 2026
a33133b
feat(chat): add room catch-up sync that creates missing chat blocks
AndyScherzinger Aug 2, 2026
66a1044
feat(chat): support fetching messages without clearing notifications
AndyScherzinger Aug 2, 2026
49aad87
feat(conversations): prefetch unread messages during room list sync
AndyScherzinger Aug 2, 2026
3bfa4f7
feat(conversations): guard message prefetch with caps and saver modes
AndyScherzinger Aug 2, 2026
2ede0ae
fix(chat): trust local cache on chat open and fetch only the delta
AndyScherzinger Aug 2, 2026
12b296f
fix(chat): do not block initial message load on websocket connect
AndyScherzinger Aug 2, 2026
534f2d6
feat(notifications): prefetch chat messages on push receipt
AndyScherzinger Aug 2, 2026
2df1d00
fix(conversations): guard conversation deletion against empty sync
AndyScherzinger Aug 2, 2026
4d5f4b4
fix(chat): reconcile chat blocks when expiring messages
AndyScherzinger Aug 2, 2026
7c4c370
fix(chat): derive insurance fetches from the chat blocks
AndyScherzinger Aug 2, 2026
c09a639
test(chat): cover message sync component
AndyScherzinger Aug 2, 2026
acd87f7
feat: Add extra logging to spot pre-fetching in debug mode
AndyScherzinger Aug 4, 2026
bac2d79
refactor(chat): restructure catch-up guards to satisfy ReturnCount
AndyScherzinger Aug 4, 2026
9a519fb
feat(chat): coalesce bursts of background message catch-ups
AndyScherzinger Aug 6, 2026
733a880
fix(chat): track insurance anchor from http syncs only
AndyScherzinger Aug 9, 2026
22686cc
fix(chat): loop delta fetches until the backlog is closed
AndyScherzinger Aug 9, 2026
5bd38f0
fix(notifications): catch up the pushed thread, not only the room
AndyScherzinger Aug 9, 2026
a0f0d9c
chore: remove unrelated code change
AndyScherzinger Aug 9, 2026
fb4d77d
fix(chat): don't merge closeBacklog's fallback range with the backlog…
mahibi Aug 10, 2026
d40d3c9
fix(chat): don't skip the initial backlog fetch on a stale lastMessage
mahibi Aug 11, 2026
36942f3
refactor(chat): extract loadInitialMessages branches into named funct…
mahibi Aug 11, 2026
a78dd14
refactor(chat): rename closeBacklog to tryCloseBacklog
mahibi Aug 11, 2026
dbdf2b5
fix(chat): seed read receipts from the conversation on chat open
AndyScherzinger Aug 13, 2026
61ba4e8
fix(chat): update read receipts from the conversation while a chat is…
AndyScherzinger Aug 13, 2026
ea40bb0
refactor(chat): move push message catch-up into a dedicated worker
AndyScherzinger Aug 13, 2026
3f6a24d
fix(chat): merge connected chat blocks atomically
AndyScherzinger Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class ChatBlocksDaoTest {
newestMessageId = searchedChatBlock.newestMessageId
)

assertEquals(5, results.first().size)
assertEquals(5, results.size)
}

@Test
Expand Down Expand Up @@ -314,7 +314,7 @@ class ChatBlocksDaoTest {
newestMessageId = searchedChatBlock.newestMessageId
)

assertEquals(1, resultsForThreadIdNull.first().size)
assertEquals(1, resultsForThreadIdNull.size)

val resultsForThreadId123 = chatBlocksDao.getConnectedChatBlocks(
internalConversationId = conversation1.internalId,
Expand All @@ -323,7 +323,7 @@ class ChatBlocksDaoTest {
newestMessageId = searchedChatBlock.newestMessageId
)

assertEquals(2, resultsForThreadId123.first().size)
assertEquals(2, resultsForThreadId123.size)
}

@Test
Expand Down Expand Up @@ -384,7 +384,7 @@ class ChatBlocksDaoTest {
threadId = null,
oldestMessageId = 10,
newestMessageId = 35
).first()
)
assertEquals(3, connectedBlocks.size)

val mergedBlock = ChatBlockEntity(
Expand Down Expand Up @@ -412,10 +412,121 @@ class ChatBlocksDaoTest {
threadId = null,
oldestMessageId = 10,
newestMessageId = 40
).first().size
).size
)
}

@Test
fun testUpsertAndMergeConnectedChatBlocksMergesOverlappingBlocks() =
runTest {
val user = createUserEntity("account1", "Account 1")
usersDao.saveUser(user)
val account1 = usersDao.getUserWithUserId("account1").blockingGet()

conversationsDao.upsertConversations(
account1.id,
listOf(
createConversationEntity(
accountId = account1.id,
token = "abc",
roomName = "Conversation One"
)
)
)

val conversation = conversationsDao.getConversationsForUser(account1.id).first()[0]

chatBlocksDao.upsertChatBlock(
ChatBlockEntity(
internalConversationId = conversation.internalId,
accountId = conversation.accountId,
token = conversation.token,
threadId = null,
oldestMessageId = 10,
newestMessageId = 20,
hasHistory = false
)
)
chatBlocksDao.upsertChatBlock(
ChatBlockEntity(
internalConversationId = conversation.internalId,
accountId = conversation.accountId,
token = conversation.token,
threadId = null,
oldestMessageId = 25,
newestMessageId = 35,
hasHistory = true
)
)

// the new block overlaps both existing blocks, so all three must merge into one
chatBlocksDao.upsertAndMergeConnectedChatBlocks(
ChatBlockEntity(
internalConversationId = conversation.internalId,
accountId = conversation.accountId,
token = conversation.token,
threadId = null,
oldestMessageId = 18,
newestMessageId = 27,
hasHistory = true
)
)

val blocks = chatBlocksDao.getChatBlocksForConversation(conversation.internalId)
assertEquals(1, blocks.size)
assertEquals(10L, blocks[0].oldestMessageId)
assertEquals(35L, blocks[0].newestMessageId)
assertEquals(false, blocks[0].hasHistory)
}

@Test
fun testUpsertAndMergeConnectedChatBlocksKeepsDisjointBlocksSeparate() =
runTest {
val user = createUserEntity("account1", "Account 1")
usersDao.saveUser(user)
val account1 = usersDao.getUserWithUserId("account1").blockingGet()

conversationsDao.upsertConversations(
account1.id,
listOf(
createConversationEntity(
accountId = account1.id,
token = "abc",
roomName = "Conversation One"
)
)
)

val conversation = conversationsDao.getConversationsForUser(account1.id).first()[0]

chatBlocksDao.upsertChatBlock(
ChatBlockEntity(
internalConversationId = conversation.internalId,
accountId = conversation.accountId,
token = conversation.token,
threadId = null,
oldestMessageId = 10,
newestMessageId = 20,
hasHistory = true
)
)

chatBlocksDao.upsertAndMergeConnectedChatBlocks(
ChatBlockEntity(
internalConversationId = conversation.internalId,
accountId = conversation.accountId,
token = conversation.token,
threadId = null,
oldestMessageId = 30,
newestMessageId = 40,
hasHistory = true
)
)

val blocks = chatBlocksDao.getChatBlocksForConversation(conversation.internalId)
assertEquals(2, blocks.size)
}

private fun createUserEntity(userId: String, userName: String) =
UserEntity(
userId = userId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ interface ChatMessageRepository : LifecycleAwareManager {

fun updateConversation(conversationModel: ConversationModel)

suspend fun loadInitialMessages(withNetworkParams: Bundle, isChatRelaySupported: Boolean)
suspend fun loadInitialMessages(withNetworkParams: Bundle)

suspend fun startMessagePolling(hasHighPerformanceBackend: Boolean)

Expand Down
Loading
Loading