Чат пропускает блок сообщений, когда кто-то пишет, пока вы пролистываете непрочитанную историю

Когда в канале или личных сообщениях непрочитанных сообщений больше, чем загружается на первой странице, и кто-то пишет сообщение, пока вы прокручиваете историю, все сообщения между концом загруженной страницы и новым живым сообщением пропускаются. Список перепрыгивает от старых сообщений сразу к живому (часто спустя несколько часов), и ничто не указывает на то, что что-то отсутствует. Перезагрузка канала заполняет пробел по одной странице за раз, поэтому для большого пробела требуется несколько перезагрузок.

Это происходит независимо от браузера или типа устройства (проверено на Firefox Desktop, Android и Chrome Android PWA).

Шаги для воспроизведения:

  1. Пользователь A и пользователь B делят канал или личные сообщения. Пользователь A читает его до последнего сообщения, затем выходит.
  2. Пользователь B отправляет более 60 сообщений.
  3. Пользователь A открывает личные сообщения. Загружается область вокруг его последнего прочитанного сообщения (25 новых сообщений ниже).
  4. Прежде чем прокрутить до конца загруженной страницы, пользователь B отправляет еще одно сообщение.
  5. Пользователь A прокручивает вниз.

Ожидаемое поведение: ниже загружается следующая страница из 25 сообщений, и так далее, пока не будет достигнуто живое сообщение.

Фактическое поведение: живое сообщение появляется сразу после загруженной страницы. Достижение конца запрашивает сообщения после живого сообщения, ответ пустой, can_load_more_future становится false, и более 35 сообщений между ними никогда не отображаются. Поскольку живое сообщение теперь видно, last_read_message_id переходит за пробел, поэтому индикатор непрочитанных также сбрасывается.

Вот моя лучшая попытка понять причину:

После коммита примерно в декабре 2025 года («FEATURE: respect user presence when chat is on screen», #36652), ChatChannel#onNewMessage всегда направляет сообщение через ChatPaneState#handleIncomingMessage, который вызывает addMessage при автоматической прокрутке, а в противном случае — через maintainScrollPosition.

ChatMessagesLoader#computeNextTargetMessage использует последнее не staging-сообщение в списке в качестве курсора для страницы FUTURE. Как только живое сообщение оказывается в конце частично загруженного списка, этот курсор перепрыгивает через всю незагруженную историю.

Затем Chat::MessagesQuery.query_paginated_messages устанавливает can_load_more_future только для страниц FUTURE, а can_load_more_past только для страниц PAST. Для другого значения возвращается nil. ChatMessagesLoader#loadMore безусловно назначает оба значения из result.meta, поэтому загрузка одной страницы PAST (немного вверх после открытия на уровне последнего прочитанного сообщения) устанавливает canLoadMoreFuture в null, даже если история ниже всё ещё не загружена. Дальнейшая пагинация работает, так как loadMore проверяет === false.

Помимо этого, есть отдельная, более старая ошибка, когда #sendNewMessage добавляет сообщение в конец списка в статус staging без загрузки последней страницы, если canLoadMoreFuture равно true. После подтверждения оно становится курсором FUTURE, поэтому возникает тот же пропуск, если вы отвечаете на старые сообщения в просматриваемой истории.

Наше сообщество не ориентировано в первую очередь на чат, но наши пользователи часто перечитывают сообщения, поэтому пропуск сообщений в продолжающейся беседе, в которой вы не участвовали, является проблемой. Я полагаю, что это будет большей проблемой для сообществ, где чат является основным средством общения, особенно учитывая новую возможность устанавливать чат как домашнюю страницу.