Skip to content

fix(chat): SSE 重连后保留聊天滚动位置 - #165

Open
SsparKluo wants to merge 1 commit into
lehhair:mainfrom
SsparKluo:fix/sse-reconnect-keep-scroll
Open

fix(chat): SSE 重连后保留聊天滚动位置#165
SsparKluo wants to merge 1 commit into
lehhair:mainfrom
SsparKluo:fix/sse-reconnect-keep-scroll

Conversation

@SsparKluo

Copy link
Copy Markdown
Contributor

问题

SSE 断连重连成功后(哪怕只断了几秒),聊天区域会被整体卸载重挂,滚动位置丢失,表现为页面被「重置」。

移动端网络不稳定时尤其明显:蜂窝/Wi-Fi 切换、信号波动都会触发短暂断连,用户正在阅读长会话时,一次网络抖动就会导致阅读位置丢失。桌面端用 Tunnel/内网穿透访问后端时同样会复现。

非 streaming 状态下 100% 复现。

根因

重连成功后的调用链:

  1. onReconnected(useChatSession)调用 loadSession(sessionId, { force: true }),补齐断连期间丢失的消息
  2. loadSession(useSessionManager)入口处无条件 messageStore.setLoadState(sid, 'loading')。代码里唯一的豁免分支是「正在 streaming 且已加载过 baseline」,但重连前 markAllSessionsStale() 已把所有 session 标记 isStale,导致该豁免永远不生效
  3. ChatPane 中 messagesReady = loadState === 'loaded' || loadState === 'error'loading 期间 chatAreaMountKey 变为 null,ChatArea 整个卸载、原地渲染 spinner
  4. 拉取完成后 ChatArea 带 key 重新挂载,virtualizer 全新初始化,没有任何滚动恢复逻辑 → 位置重置

本质:「补数据」(backfill)和「首屏加载」两个不同语义共用了同一个 loading 状态转换。

修复

useSessionManager.loadSession 中,当 force 刷新且该 session 已有渲染中的消息时,跳过 setLoadState(sid, 'loading')

  • loadState 保持 loaded,ChatArea 不卸载,滚动位置保留
  • 数据刷新语义不变:拉取完成后仍通过 setMessages 以服务端数据覆盖本地
  • 无消息时(首次加载、加载失败后的重试)行为完全不变,仍走 loading 流程

测试

  • 新增回归测试:force + 已有渲染消息 → 不触发 setLoadState('loading'),且仍完成 setMessages
  • npm run typecheck 通过;全量 vitest 通过(596 passed)

A successful reconnect triggers loadSession(force), which flipped
loadState back to 'loading'; ChatPane unmounts ChatArea in that
state, so even a brief disconnect lost the scroll position on
remount. Skip the loading transition when force-reloading a session
that already has rendered messages - backfill, not first load.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant