当一个频道或私信中有未读消息,且其数量超过第一页加载的内容时,如果在您滚动浏览该历史记录时有人发布了新消息,那么已加载页面末尾与新实时消息之间的所有消息都会被跳过。列表会直接从较早的消息跳到新的实时消息(通常相隔数小时),并且没有任何迹象表明有内容缺失。重新加载频道会一次一页地填补这些空缺,因此较大的空缺需要多次重新加载才能补全。
此问题与浏览器或设备类型无关(已在 Firefox 桌面版和安卓版以及 Chrome 安卓版 PWA 上测试)。
复现步骤:
- 用户 A 和用户 B 共用一个私信或频道。用户 A 阅读到最新消息后离开。
- 用户 B 发布了 60 条以上的消息。
- 用户 A 打开私信。页面会在其最后阅读的位置附近加载(下方有约 25 条较新的消息)。
- 在滚动到该已加载页面的底部之前,用户 B 又发布了一条消息。
- 用户 A 向下滚动。
预期:在下方加载接下来的 25 条消息页面,以此类推,直到实时消息。
实际:实时消息直接出现在已加载页面之后。滚动到底部时会请求实时消息之后的消息,响应为空,can_load_more_future 变为 false,而那 35+ 条中间的消息从未显示。由于实时消息现在可见,last_read_message_id 会推进到空缺之后,因此未读指示器也会清除。
以下是我对为何发生此问题的最佳分析:
自 2025 年 12 月左右的一次提交(“FEATURE: respect user presence when chat is on screen”, #36652)以来,ChatChannel#onNewMessage 始终通过 ChatPaneState#handleIncomingMessage 路由消息,后者在自动滚动时调用 addMessage,否则通过 maintainScrollPosition 处理。
ChatMessagesLoader#computeNextTargetMessage 使用列表中最后一条非暂存(non-staged)消息作为 FUTURE 页面的光标。一旦实时消息位于部分加载列表的末尾,该光标就会跳过所有未加载的历史记录。
此外,Chat::MessagesQuery.query_paginated_messages 仅为 FUTURE 页面设置 can_load_more_future,仅为 PAST 页面设置 can_load_more_past,另一项则为 nil。ChatMessagesLoader#loadMore 无条件地从 result.meta 分配这两项,因此单个 PAST 页面(在打开位于最后阅读位置后稍向上滚动时)会在下方历史记录仍未加载的情况下将 canLoadMoreFuture 设置为 null。由于 loadMore 检查的是 === false,进一步的分页仍然有效。
除此之外,还有一个独立的、更旧的 bug:当 canLoadMoreFuture 为 true 时,#sendNewMessage 会将消息暂存在列表末尾,而不加载最新页面。一旦消息被确认,它就成为 FUTURE 光标,因此如果您在正在阅读的历史记录中回复较旧的消息,也会发生同样的跳过情况。
我们的社区主要并非以聊天为核心,但我们的用户经常回溯阅读,因此在您未在场的持续对话中遗漏消息是一个问题。我认为这对于以聊天为主的社区来说会是更大的问题,尤其是考虑到现在可以将聊天设置为主页这一新功能。