Chat skips a block of messages when someone posts while you're catching up on unread history

AI-generated summary

Luna_Mora reports a bug where Chat skips a block of messages when a new message arrives while a user is catching up on unread history. If a channel or DM has more unread messages than the initial load, and someone posts a new message before the user scrolls to the bottom of the current page, the view jumps directly to the new live message. The intermediate messages are skipped, and the list does not indicate any missing content. Reloading the channel gradually fills the gap, which is inefficient for large backlogs. The issue was reproduced across Firefox Desktop, Android, and Chrome Android PWA.

The reporter provides detailed steps to reproduce: after leaving a chat and returning to find 60+ new messages, opening the chat loads around the last-read message. If a new message is posted before scrolling through the loaded page, the next load request jumps to the future, bypassing the unloaded history. Consequently, the unread indicator clears incorrectly as last_read_message_id advances past the gap.

Technically, Luna_Mora identifies a regression introduced in December 2025 (#36652). The ChatChannel#onNewMessage method now routes incoming messages through ChatPaneState#handleIncomingMessage. When a live message is added to a partially loaded list, ChatMessagesLoader#computeNextTargetMessage uses this message as the cursor for fetching future pages. This causes subsequent pagination requests to skip the unloaded historical messages.

Additionally, there is an existing bug where sending a new message stages it at the end of the list without loading the latest page if canLoadMoreFuture is true. Once confirmed, this message becomes the future cursor, causing similar skips when replying to older messages. The reporter emphasizes that this is particularly problematic for communities where chat is a primary communication tool or set as the homepage, as it leads to significant information loss during catch-up.

When a channel or DM has more unread messages than the first page loads, and someone posts while you’re scrolling through that history, every message between the end of the loaded page and the new live message is skipped. The list jumps from the older messages straight to the live one (often hours later), and nothing indicates that anything is missing. Reloading the channel fills the gap back in one page at a time, so a large gap takes several reloads.

This occurs independent of browser or device type (tested on Firefox Desktop and Android and Chrome Android PWA).

Steps to reproduce:

  1. User A and user B share a DM or channel. User A reads it up to the latest message, then leaves.
  2. User B posts 60+ messages.
  3. User A opens the DM. It loads around their last-read message (25 newer messages below).
  4. Before scrolling to the bottom of that loaded page, user B posts one more message.
  5. User A scrolls down.

Expected: the next 25-message page loads below, and so on until the live message.

Actual: the live message appears directly after the loaded page. Reaching the bottom requests messages after the live message, the response is empty, can_load_more_future becomes false, and the 35+ messages between are never shown. Because the live message is now visible, last_read_message_id advances past the gap, so the unread indicator clears as well.

Here is my best attempt at figuring out why this is happening:

Since a commit around December 2025 (“FEATURE: respect user presence when chat is on screen”, #36652), ChatChannel#onNewMessage always routes the message through ChatPaneState#handleIncomingMessage, which calls addMessage when auto-scrolling and via maintainScrollPosition otherwise.

ChatMessagesLoader#computeNextTargetMessage uses the last non-staged message in the list as the cursor for a FUTURE page. Once a live message sits at the end of a partially loaded list, that cursor jumps past all the unloaded history.

Then, Chat::MessagesQuery.query_paginated_messages only sets can_load_more_future for FUTURE pages and can_load_more_past for PAST pages. The other is nil. ChatMessagesLoader#loadMore assigns both from result.meta unconditionally, so a single PAST page (scrolling up a little after opening at last-read) sets canLoadMoreFuture to null while history below is still unloaded. Further pagination still works because loadMore checks === false.

On top of that, there is a separate, older bug where #sendNewMessage stages the message at the end of the list without loading the latest page when canLoadMoreFuture is true. Once confirmed, it becomes the FUTURE cursor, so the same skip happens if you reply to older messages in the history you’re reading.

Our community is not primarily chat-focused, but our users do back read frequently, so missing messages in an ongoing conversation you weren’t present for is a problem. I imagine it’ll be a bigger problems for communities that are primary-chat, especially given the new ability to set the homepage as chat.