fix(chat): SSE 重连后保留聊天滚动位置 - #165
Open
SsparKluo wants to merge 1 commit into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
SSE 断连重连成功后(哪怕只断了几秒),聊天区域会被整体卸载重挂,滚动位置丢失,表现为页面被「重置」。
移动端网络不稳定时尤其明显:蜂窝/Wi-Fi 切换、信号波动都会触发短暂断连,用户正在阅读长会话时,一次网络抖动就会导致阅读位置丢失。桌面端用 Tunnel/内网穿透访问后端时同样会复现。
非 streaming 状态下 100% 复现。
根因
重连成功后的调用链:
onReconnected(useChatSession)调用loadSession(sessionId, { force: true }),补齐断连期间丢失的消息loadSession(useSessionManager)入口处无条件messageStore.setLoadState(sid, 'loading')。代码里唯一的豁免分支是「正在 streaming 且已加载过 baseline」,但重连前markAllSessionsStale()已把所有 session 标记isStale,导致该豁免永远不生效messagesReady = loadState === 'loaded' || loadState === 'error',loading期间chatAreaMountKey变为null,ChatArea 整个卸载、原地渲染 spinnerkey重新挂载,virtualizer 全新初始化,没有任何滚动恢复逻辑 → 位置重置本质:「补数据」(backfill)和「首屏加载」两个不同语义共用了同一个 loading 状态转换。
修复
useSessionManager.loadSession中,当force刷新且该 session 已有渲染中的消息时,跳过setLoadState(sid, 'loading'):loaded,ChatArea 不卸载,滚动位置保留setMessages以服务端数据覆盖本地测试
setLoadState('loading'),且仍完成setMessagesnpm run typecheck通过;全量 vitest 通过(596 passed)