优化: todo 销账 B-260617-09+UX-260617-01 虚拟滚动 minHeight 防御修复

This commit is contained in:
2026-06-17 23:34:33 +08:00
parent 8abcd567a6
commit 02bee38738

View File

@@ -154,10 +154,52 @@
- [ ] B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`(本轮部分累积)写回 ChatMessage回传下一轮 DeepSeek 会看到不完整推理。属异常路径(网络中断保文),保文后已加"响应不完整"系统提示,用户大概率重发。影响小,登记观察。— src-tauri/src/commands/ai/agentic.rs(:765)
### 🔧 2026-06-17 对话标题不更新(还叫"新对话"·待运行时验证)
> 用户报:连续对话很多内容 + 刷新重启后,标题仍"新对话"。session-role-diagnose-only排查链路 + 记 todo。
**排查结论(排除项)**
-**save_conversation 不覆盖 title**(核验 conversation.rs:166-194已落库分支只改 messages/updated_at/token/model**不碰 title**;首次落库 `title:None` :194。排除"save 重置 title"。
-**title 初始 None**(懒创建不落库 commands.rs:1286 + 首次落库 None→ ensure_conversation_title :38-42 `is_some()`=false → **不跳过**,应生成。
-**extract_title 兜底**title.rs:157 取首句前 30 字)即使 LLM 失败也应落库非"新对话"。
**最可能根因(待运行时 tracing 验证)**
- **嫌疑 a**ensure_conversation_title 在 spawn 后台 taskagentic.rs:924-931`generate_title_via_llm`title.rs:90/120 网络耗时)**未完成时用户已刷新/重启** → 标题未落库 → 重启读 NULL → "新对话"。
- **嫌疑 b**title.rs:95 `let _ = conv_repo.update_field(...)` 落库失败被吞(无日志),标题没入库但 :96 emit 照发(前端短暂更新,重启丢)。
- **嫌疑 c**title.rs:141-142 双层信号量 acquire_global+acquire_per_conv 阻塞permit 未释放)→ ensure 永不完成。
**矛盾点**:用户"很多内容多次对话"应多次触发 ensureagentic.rs:462/776/843/930 四点),总有完成的——除非每次刷新打断或信号量持续阻塞。**确诊需运行时日志**grep tracing 看"标题生成跳过"(title.rs:72)/"更新对话失败"(conversation.rs:186) / ensure 是否触发。
**修法方向(待确诊后)**:① ensure 改同步loop 内 await完成后再 emit Completed② :95 update_field 失败加 error 日志 ③ 排查信号量阻塞。
- [ ] B-260617-17 [P2] — **对话标题不更新(刷新/重启后仍"新对话"**。save 不覆盖 title已排除title 初始 None 应触发生成。主嫌疑ensure 后台 LLM 生成未完成即刷新/重启(网络耗时)→ 标题未落库。待运行时日志确诊 ensure 是否执行 + update 是否成功。— src-tauri/src/commands/ai/title.rs(:27,:90,:95) · agentic.rs(:924-931 spawn 后台) · conversation.rs(:166-194 save 不碰 title)
### 🔧 2026-06-17 知识库列表首次空(确定性 bug·根因确诊
> 用户报:打开系统进知识库列表数据空,加载完成后仍不显示;需点"待处理"再回"知识库"才显示。session-role-diagnose-only排查链路根因确诊。
**根因(确定性)**`stores/knowledge.ts:31` `_itemsReqSeq` 序号被 **loadList(:62)/loadCandidates(:80)/search(:92) 三者共用**(都 `++_itemsReqSeq`),设计为"丢弃旧响应防竞态",但漏了并发覆盖场景:
**onMountedKnowledge.vue:521-523并发调用** `loadList()` + `loadCandidates()`
1. loadList 发起seq=1, await list
2. loadCandidates 发起(`++_itemsReqSeq`→2**覆盖**
3. loadList 响应回来 → knowledge.ts:67 `if (seq !== _itemsReqSeq)` → 1≠2 → **return 丢弃**`state.items` 永空
4. loadCandidates 响应 → seq=2 匹配 → 写 `state.candidates`
→ 首次进入 `listItems` computedKnowledge.vue:297-298 读 `store.items`)空。点"待处理"(inbox)→ switchTab loadCandidates再回"知识库"(library)→ switchTab **单独 loadList无并发**→ seq 匹配→写 items→**显示**。完美解释"切走再回才显示"。
注释 knowledge.ts:29-30「items 序号覆盖 loadList/loadCandidates/searchcandidates 与 items 共用同一队列」=有意共用序号,但没料到 loadList 被并发 loadCandidates 覆盖丢弃。
**修法(确定性·唯一正确解·低风险)**分序号——loadList/search都写 `state.items`)共用 `_itemsSeq`loadCandidates`state.candidates`)用独立 `_candidatesSeq`。各自防竞态,互不丢弃。改 knowledge.ts:31 拆两序号 + loadList/search/loadCandidates 各用对应序号。
- [ ] **🚨 B-260617-18 [P1]** — **知识库列表首次进入必空**`_itemsReqSeq` 序号 loadList/loadCandidates/search 共用knowledge.ts:31/:62/:80/:92onMounted 并发 loadList+loadCandidates 时 loadList 的 seq 被 loadCandidates 的 `++` 覆盖 → loadList 响应 :67 `seq !== _itemsReqSeq` 丢弃 → state.items 永空 → 列表空。切 tab 再回switchTab 单独 loadList 无并发)才显示。**确定性 bug**,修法:拆 `_itemsSeq`(loadList/search) + `_candidatesSeq`(loadCandidates) 分队列防互相丢弃。— src/stores/knowledge.ts(:31 序号共用·:62/:80/:92·:67 丢弃逻辑) · src/views/Knowledge.vue(:521-523 onMounted 并发)
### 🔧 2026-06-17 aichat 消息全量重叠5角度深入分析·未实施
> 用户报 aichat 对话「大量重叠」「不单间距问题」。session-role-diagnose-only5 角度并行论证后记录。
> **🔥 2026-06-17 补充触发场景(展开全部·同根因)**:用户报"展开全部"折叠段archived/compressed**展开内容未把后续消息往下挤压,而是与未展开消息重叠**。机制同主根因:展开段 → `renderItems` computed 新增 msg itemkey=`'e-'+seg.key+'-'+m.id`AiChat.vue:2586→ 新 item 注册 sentinel`itemMeta` height 初始 0useAiVirtualScroll.ts:104→ 高度竞态 → slot 塌 0 → 内容 overflow 显示 + 后续上移 = 重叠。**与"第二次发送"触发的重叠同根(虚拟滚动 slot 高度塌陷B-260617-09 方案 A卸载 minHeight fallback/ C禁虚拟滚动验证可一并解决**。
> **🔥 2026-06-17 用户精确场景收敛(根因定位·待 DevTools 终验)**用户给出精确触发模式——①user 发 msg1→AI 回 reply1(一或多卡片) ②user **再次发送** msg2 时,**msg2 重叠在 reply1 之上** ③reply2 紧跟重叠 msg2 后 ④后续连续重叠。此场景锁定触发时机=**第二次发送reply1 从 pinned 移出的瞬间)**,根因链收敛:
> - **CSS 兜底缺失(确定)**`.ai-msg-slot`AiChat.vue:3174仅 `display:flex;flex-direction:column`**无 min-height/height/position**。slot 高度完全靠内容撑;内容卸载时高度**唯一依赖**虚拟滚动 IO 回调设的 inline `minHeight`useAiVirtualScroll.ts:74无 CSS 静态兜底。
> - **minHeight 守卫跳过(核心嫌疑)**useAiVirtualScroll.ts:73 卸载分支 `if (meta && meta.height > 0 && meta.el.isConnected)`——**height=0 时跳过设 minHeight** → slot 塌 0 → 后续 flex 项上移 → 叠在 reply1 内容overflow 显示)上 = 用户所见"msg2 重叠在 reply1 上"+后续连续重叠。
@@ -398,7 +440,7 @@ gap: 8px;
| P2 | B: v-else-if 快速切换 DOM 复用异常 | 低 | 临时改 v-else-if 为 v-else始终渲染验证 |
| P3 | 原①: IO 漏 observe | 低→中 | 已修正为"IO 正常但时序竞态" |
- [ ] B-260617-09 [P1] — **aichat 消息全量重叠**。用户报「大量重叠」「不单间距」。5 角度深入论证排除 CSS/markdown/数据/WebView2 后,**最高概率根因更新为:虚拟滚动 IO 与 RO 时序竞态**——IO 先于 RO 首次回调触发卸载分支时 `itemMeta.height` 仍为 0 → `:73` `height>0` 守卫失败 → 不设 minHeight → slot 塌 0 高 → 下条消息上浮重叠。次 probablev-html DOM 破坏marked+purify 产出异常 HTML 破坏 bubble 闭合标签)。**修法方向**①IO 卸载分支加 fallback minHeight如取 el.offsetHeight 或固定最小值 40px②临时禁用虚拟滚动验证根因 ③DevTools 断点验证 IO/RO 时序。— src/composables/ai/useAiVirtualScroll.ts(:68-76 IO卸载分支 height守卫) + src/components/AiChat.vue(:400-401 shouldRenderMsg条件渲染) + src/composables/useMarkdown.ts(:188 renderMd sanitize)
- [x] ✅(wf wf_9e13176f+主代核验·commit 8abcd56·防御修复已落地) B-260617-09 [P1] — **aichat 消息全量重叠**【根因嫌疑=虚拟滚动 IO 与 RO 时序竞态:IO 先于 RO 首次回调触发卸载分支时 meta.height===0 → 不设 minHeight → slot 塌 0 高 → 下条消息上浮重叠。**防御修复已落地**:useAiVirtualScroll.ts IO 卸载分支(line 77-79)三层 fallback(height>0 实测→offsetHeight→40px)+ registerSentinel 首次挂载(line 110-111)40px 兜底。sentinel 永不塌 0。vue-tsc EXIT 0。**待用户实测确认**:若重叠消除则根因确认;若仍重叠则 DevTools 验证次 probable(v-html DOM 破坏)或其他根因】 — src/composables/ai/useAiVirtualScroll.ts(:68-82 IO卸载分支 + :104-112 registerSentinel)
### 🔧 2026-06-17 useAiSend.ts 代码审查(仅走查·未实施)
@@ -1179,7 +1221,7 @@ gap: 8px;
#### 🔴 已确认交互 Bug
- [ ] **UX-260617-01 [P0]🎯****消息/工具卡大量重叠堆叠(用户实测确认)**。5 角度深入分析todo.md L116-180+CSS 声明链路正常、scoped 无冲突、DOM 结构正确 → 排除常规原因。**核心嫌疑**虚拟滚动 IO 半激活态不一致——部分 item 被 IO 管理(可卸载)、部分不被管理(永远渲染) + height=0 时 minHeight 未设置 → slot 塌 0 → 重叠。**需复现定位确切触发路径后修复**。— src/components/AiChat.vue + src/composables/ai/useAiVirtualScroll.ts
- [x] ✅(commit 8abcd56·同 B-260617-09 根因·防御修复已落地·待用户实测确认) **UX-260617-01 [P0]🎯****消息/工具卡大量重叠堆叠(用户实测确认)**核心嫌疑=虚拟滚动 IO/RO 时序竞态致 slot 塌 0。**防御修复已落地**(useAiVirtualScroll.ts IO 卸载分支 minHeight 三层 fallback + registerSentinel 首次挂载 40px 兜底)。vue-tsc EXIT 0。**待用户实测**:若消除则根因确认,否则 DevTools 验证次嫌疑(v-html DOM 破坏/CSS)】
- [x] **UX-260617-02 [P1]🎯****CR-59 审查发现:`onPoolWeightChange` 缺乐观更新 revert 逻辑** — Settings.vue:643-656 weight 输入 debounce 300ms 调 IPC成功后未像 onPoolToggle 一样 revert 到服务端值。IPC 失败或延迟时 UI 显示旧值而非回滚。**修法**:补 revert 分支(对齐 onPoolToggle 模式)。— src/views/Settings.vue:643-656
- [x] **UX-260617-03 [P1]🎯****copyMsgContent 失败误报「已复制」(已记录未修)**`AiChat.vue:1037-1040` clipboard.writeText 权限拒绝 → catch 仍 showToast('copied')。用户见"已复制"去粘贴发现空。**修法**catch 改失败文案或静默。(已在 AI 链路审查 B-260615-46 记录,提升优先级至此区)。— src/components/AiChat.vue:1037-1040
- [x] **UX-260617-04 [P1]🎯****offsetToDOMPosition 恒返回 root 致选区恢复错位(已记录未修)**`AiChat.vue:908-921` createTreeWalker 后 currentNode 初始=root 非 Text → ?? 恒取左 → 循环无 nextNode 推进 → 选区恒错位。UX-2025-01 功能实际失效。(已在 B-260615-47 记录,提升优先级至此区)。— src/components/AiChat.vue:908-921