Skip to content

fix: 会话回答完成瞬间气泡闪烁(视图身份跨完成时刻保持稳定) - #271

Merged
taekchef merged 1 commit into
mainfrom
codex/fix-agent-streaming-flicker
Aug 20, 2026
Merged

fix: 会话回答完成瞬间气泡闪烁(视图身份跨完成时刻保持稳定)#271
taekchef merged 1 commit into
mainfrom
codex/fix-agent-streaming-flicker

Conversation

@taekchef

@taekchef taekchef commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

问题

会话回答完成的瞬间气泡闪烁:排版好的内容闪断成无 KaTeX/表格/代码高亮的原生兜底文本,气泡高度塌缩回 44pt 加载高度再弹开,随后新 WebView 冷启动完毕二次跳变。含公式/代码/表格的长回答最明显,纯文本短回答不易察觉。

根因

chat 窗格(agentMessageRow)与浮层面板各自用两个不同类型的视图分支渲染生成中/已完成消息(e058c61 引入)。SwiftUI 条件分支翻转时整棵子树销毁重建:

  • 流式期间维护的 WKWebView 在 completionState 翻转瞬间被 dismantle;
  • 新挂载的 AgentMessageMarkdownText 在 finalizedRendererReady 之前显示原生兜底层(无富渲染样式);
  • 高度种子缓存流式期间不写入,新挂载回到 44pt 加载高度;
  • 14941e7 建立的「同一 Markdown 表面跨越流式→完成」契约(keepsMarkdownSurfaceMounted + finishStreamingMarkdown)因外层 remount 无法生效。

改动(仅 Sources/WeiBei/Views/NotesAgentView.swift)

  • 两个 surface 各合并为单一行类型(AgentMessageBubble / FloatingSelectionMessageRow),liveStreamingText / liveActivityText 只在 .generating 时传值,完成后传 nil
  • 隔离保持:只有生成中的行观察真实 AgentStreamingState;完成的行改观察共享的 inertAgentStreamingState(永不发布)。@ObservedObject 装上即订阅、与 body 是否读取无关,因此不能简单让完成行继续观察全局 live state——那会把 e058c61 消除的「每个 token 广播给所有历史气泡」请回来。
  • 删除因此成为死代码的 AgentBubble.streaming 参数(体内从未读取)与 floatingText(for:)

共享核心文件占用

无(仅 NotesAgentView.swift,不在共享核心面)。

验证

  • swift build
  • swift run WeiBeiSelfCheck
  • 真实 App 冒烟(待验收):问一条含代码块/公式的长回答,观察说完最后一字的瞬间——文字应安静停住,无闪断、无裸文本、无高度塌缩。chat 窗格与选区浮窗都试。
  • 可选工程验证:观察 webview.markdown_create / webview.markdown_destroy perf 事件——同一条消息从生成到完成不得出现旧实例 destroy + 新实例 create。

已知残留 / 未做

  • final snapshot 等待期原生兜底轻闪:已修复(第二轮,验收反馈后)。交接期 WebView 保持可见,原生兜底只留给冷挂载的历史消息;最终快照高度与流式末帧不同时最多有一次高度微调。
  • editor.ts streaming 插件 throttleMs: 0 导致的流式过程中格式跳变(未闭合 fence/表格中间态)不在本 PR 范围。

@taekchef

Copy link
Copy Markdown
Collaborator Author

WeiBeiSelfCheck ✅ passed

@taekchef
taekchef force-pushed the codex/fix-agent-streaming-flicker branch 2 times, most recently from 306d87d to 3ec6b75 Compare August 20, 2026 16:35
The chat pane and floating panel switched between two different view
types for generating vs completed messages. When a reply finished, the
conditional flip tore down the whole bubble subtree — WKWebView destroyed
and cold-started, native fallback text flashing in between, bubble height
collapsing to the loading seed and expanding again.

Render one row type per surface (AgentMessageBubble /
FloatingSelectionMessageRow) so the view identity — and the mounted
markdown WKWebView — survives the generating → completed flip, and the
keepsMarkdownSurfaceMounted seamless-handoff path (14941e7) becomes
reachable again.

Only the generating row observes the live AgentStreamingState; completed
rows observe a shared inert state that never publishes. @ObservedObject
subscribes regardless of body reads, so pointing finished rows at the
live state would re-broadcast every token to every mounted bubble — the
churn e058c61 set out to remove.

Keep the streaming WebView fully visible through the finalized-snapshot
handoff instead of hiding it (opacity 0.01) behind the native fallback
while awaiting the authoritative measurement. The native overlay is now
reserved for cold-mounted rows whose WebView has no content yet, so the
completion moment no longer flashes unstyled text.
@taekchef
taekchef force-pushed the codex/fix-agent-streaming-flicker branch from 3ec6b75 to 6e45d74 Compare August 20, 2026 16:36
@taekchef
taekchef marked this pull request as ready for review August 20, 2026 17:08
@taekchef
taekchef merged commit abf8f4a into main Aug 20, 2026
2 checks passed
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