# todo 已完成项归档(2026-06) > 从 docs/todo.md 拆出: 已完成 `[x]` 条目段 + 纯历史分析段。 > 维护: 随开发追加; 主看板只留未完成 `[ ]` 与导航/总览。 > 拆分日期: 2026-06-18 > 来源: docs/todo.md --- ### P0 — 阻断性 bug - [x] ✅(主代修06-17·撤销误判) **🚨 BUG-260617-01 [P0] 已修** — **`|| true` 致 400/参数错误无效重试 4 次**【已修:stream_recv.rs:433 删 `|| true` + 注释 + 单测 classify_unknown_retryable→classify_unknown_not_retryable 断言改 !;cargo test 20 passed】 — 用户实测证实(2026-06-17)。Anthropic `[1214][messages 参数非法]` 无 HTTP 前缀 → 落入 `stream_recv.rs:433` `\|\| true` → retryable=true → **空耗 4 次重试浪费 token**。前次审查误判为"有意设计"。**一行改动:删 `|| true` + 改单测断言**。— src-tauri/src/commands/ai/stream_recv.rs:433 - [x] ✅(wf wf_b0fa5730-9e9·主代适配+commit 74003c) **🚨 BUG-260617-12 [P0] 已修** — **DeepSeek reasoning_content 缺失致必 400**【全链路透传:df-ai-core(provider.rs CompletionRequest/ChatMessage/CompletionResponse/StreamChunk 加字段+5单测) → df-ai(openai_compat.rs OpenAiRequest/OpenAiMessage/OpenAiMessageResp/OpenAiStreamDelta 映射+流式解析) → anthropic_compat.rs 11处 StreamChunk/CompletionResponse 兼底) → agentic.rs(last_reasoning_content 初始化→stream_one_provider 注入请求→outcome 提取→push 时回填 ChatMessage) → stream_recv.rs(reasoning_content 累积+StreamResult Complete/Partial 携带) → title.rs 字面量补齐 + df-nodes/ai_node.rs/compress/knowledge_inject 非agentic 路径 reasoning_content: None。cargo check --workspace EXIT 0 / df-ai-core 24 test pass / df-ai+df-nodes 0 test(无单测)】。调研确认 DeepSeek API 规范:reasoning_content 在 choices[].message.reasoning_content(响应)+多轮 assistant 消息必须回传(有 tool_calls 时),否则 400 "must be passed back"。】 — 14 文件 / +255 行 (commit 74003c) > **⚠ 2026-06-17 走查独立核验**:闭环逻辑逐行验明**正确**(流式累积→StreamResult→StreamOutcome→agentic loop `last_reasoning_content` 跨轮缓存→写回 ChatMessage 三路径→openai_compat 透传回 API;前端 src/ 无 reasoning_content 引用,落库不展示无 UX 泄露)。**B-260617-14 提交遗漏已修**(commit f6e4e82):补提 adversarial.rs + project.rs 的 `reasoning_content: None`,HEAD 干净 checkout 编译通过。 - [x] B-260614-01 — ~~待审批持久化根治(重启恢复)未生效~~ ✅ mission:T-260614-01 已修复(commands.rs:444 clear→retain 保其他对话 pending;ai_approve 两处 if !recovered 守卫移除;cargo check 0 err / 19 test pass)(06-14) - [x] B-260614-02 — ~~df-workflow ConditionEngine 默认 true~~ ✅ mission:T-260614-02 已修复(conditions.rs:31 `Ok(true)`→`Ok(false)` 保守拒绝;5 个原断言错误行为的测试同步改断言;df-workflow 7 test pass)(06-14) - [x] B-260614-04 — ~~df-workflow NodeRegistry::default() script 工厂 unimplemented!~~ ✅ mission:T-260614-03 已修复(删除整个 Default impl——零调用方 + 违反铁律;state.rs build_registry 已用 new() + 手动注册真实 ScriptNode)(06-14) - [x] B-260616-20 [P0] — **df-nodes 测试回归:`advance_succeeds_after_prior_status_change` 红(CR-260616-41 status 收口副作用)** ✅ **已修**(主代 task_advance_node.rs:331-332 改 `advance_status_atomic("t1","todo","in_progress",false)` 走 CAS 合法路径模拟并发推进,亲跑 `cargo test -p df-nodes` **67 passed/0 failed/1 ignored**)。原委:commit b94e74a 把 status 移出 tasks 白名单(D-260616-04 收口),`crates/df-nodes/src/task_advance_node.rs:332` 测试用 `repo.update_field("t1","status","in_progress")` 模拟「旁路并发改 status」前置,现被白名单拒 → unwrap panic。生产代码无回归,仅测试 fixture 同步。— crates/df-nodes/src/task_advance_node.rs:331(审查 CR-260616-41 复现) ### 🔧 2026-06-16 排查会话新报 bug(4 项·仅分析未实施) > 排查性质会话(session-role-diagnose-only),仅走查定位根因 + 记录待办,未改代码。 - [x] ✅(第㉖批 2026-06-16·主代理直修) B-260616-09 [P1] — **任务详情点击打开空白(路由漏注册)**。**router/index.ts 补 /tasks/:id → TaskDetail**(L50-54 新增块,6 行,参照 /projects/:id L39-43 + /ideas/:id L27-31 模式)。name 'TaskDetail' 无冲突(现有 name 集无此项),复用 icon-thunder(对齐 /ideas/:id 复用 icon-lightbulb、/projects/:id 复用 icon-apps 一致模式)。TaskDetail.vue 现挂载,Tasks.vue:57 router.push(`/tasks/${task.id}`) 不再跳未注册路由,B-18 联动刷新等功能激活(死代码转活)。改动极简确定(6 行路由表项),主代理直修省 workflow 单 agent 编排开销。vue-tsc exit 0。— src/router/index.ts - [x] ✅(第㉖批 2026-06-16) B-260616-10 [P1] — **provider 配置后 AI 面板仍提示「未配置」**。**Settings.vue loadProviders 同步刷全局 store**。L376 import useAiStore + L383 const aiStore = useAiStore()(跟随 L382 appSettings 风格)+ L420-424 loadProviders 内本地 aiProviders.value 刷新成功后 L424 await aiStore.loadProviders() 刷全局 state.providers(模块级单例 ai.ts:45,L20/L44 注释明确全应用共享同一份,AiChat 共享)。三处调用点(saveProvider L464/deleteProvider L479/setDefaultProvider L490)+ onMounted L731 均经 loadProviders 自动同步全局。无循环(useAiPanel.ts:91 loadProviders 只刷 state.providers 不回调 Settings)。无 i18n 改动(纯数据同步),try/catch + showToast 保留。vue-tsc exit 0。— src/views/Settings.vue - [x] ✅(ca9b318·wrapNakedDiff落地+第㉖批diff语言注册) B-260616-11 [P2] — **AI 消息 `-`/`+` 列表/diff 无视觉效果**。两项子项均已落地:①**裸 diff 预处理** `wrapNakedDiff`(ca9b318 useMarkdown.ts:+96 守卫4:纯`-`列表不触发/≥2行连续±/已在围栏不动/段落级处理+短路优化+缓存key用wrapped) ②**diff 围栏高亮**(第㉖批 useMarkdown.ts import diff+registerLanguage('diff')+highlightCode getLanguage('diff')→hljs addition/deletion token+github-dark.css 红绿)。— src/composables/useMarkdown.ts(:183-282 renderMd+wrapNakedDiff 全函数) - [x] ✅(第㉗批 2026-06-16·workflow) B-260616-19 [P2] — **知识库详情窄屏标题挤压成竖线(一行一字)**。三因子叠加(Knowledge.vue):①`.kn-layout` grid `grid-template-columns: 360px 1fr`(L567)——左列表死钉 360px、右详情 `1fr` 拿剩余,**无响应式断点**;窗口窄(如总宽 500px,减 padding 40+gap)详情列塌缩到 ~80px 甚至更小。②`.detail-title`(L625)`font-size:18px` + `word-break:break-word` —— 窄列里中文每字宽度 > 列宽,逐字换行 → 竖线化。③`.detail-actions`(L626)`flex-shrink:0` —— 编辑/发布/拒绝 3 按钮不缩,优先占位,把 `.detail-title-row`(`flex:1`) 挤到零头。`.kn-detail-panel`(L570-576) 无 `min-width` 兜底。改动(待定):①grid 列宽改 `minmax(240px,360px) 1fr` + `@media (max-width:760px)` 切单列(`grid-template-columns:1fr`,列表详情上下堆叠);②详情面板加 `min-width:0` 配合标题行 `min-width` 防塌缩;③可选标题字号响应式降级(18px→15px)。— src/views/Knowledge.vue。**低风险纯 CSS**,无逻辑/i18n/数据变动。 ### 🔧 2026-06-17 实测发现 bug(审批流程三连) > 用户实测:AI 对话中让 AI 创建任务并推进状态 → AI 调 `run_workflow(task_id, target_status)` → 审批卡片渲染 → 用户点批准 → handler 返回 Err → LLM 重试循环。 - [x] ✅(波9·e23b316+主代核查,cargo0err+devflow79pass,方案a2后端分支) B-260617-01 [P1] — **run_workflow 作为 AI 工具无法执行 + 无意义重试 + 审批混淆**。三个关联问题: - **问题 1**:`run_workflow` handler(tool_registry.rs:530-546)仅持有 `db: Arc`,无法访问 `AppHandle/State`(registry/event_bus/workflows Repo),返回 `Err("run_workflow 工具需经 Tauri IPC 执行...")`。审批通过后 `ai_approve`(commands.rs:329)调 `ai_tools.execute()` → handler 必然 Err → `audit_status="failed"` → 错误信息作为 tool_result 回传 LLM。工作流实际未执行,任务状态未推进。 - **问题 2**:handler Err → tool_result=错误提示 → LLM 认为失败 → 重试(1/4) → 同结果循环。工具定义未标 `no_retry`,浪费 token + 用户反复审批。 - **问题 3**:前端渲染的是 AI 工具审批(tool_call pending_approval),非工作流 HumanNode 审批。用户批准后工作流引擎从未启动,HumanNode 审批节点从未到达。 - **修复方向**:**方案 A(推荐)**:前端拦截 — `ai_approve` 中识别 `run_workflow` tool_call → 不走 `ai_tools.execute` → 直接 `invoke('run_workflow', {task_id, target_status})` → 结果回填 tool_result。**方案 B**:后端注入 State — `build_ai_tool_registry` 传入 `AppHandle` 使 handler 可直接执行。问题 2/3 随 A 自动消解。 - **代码定位**:tool_registry.rs:530-546(handler 返 Err)/ audit.rs:590-630(High risk 进 pending)/ commands.rs:329-334(ai_approve execute→Err→failed)/ agentic.rs:503-508(pending_count>0 暂停循环) — src-tauri/src/commands/ai/{tool_registry,audit,commands,agentic}.rs + src/composables/ai/useAiSend.ts ### 🔧 2026-06-16 aichat 工具卡片重复渲染排查(仅分析·未实施) > 排查性质(session-role-diagnose-only),走查定位根因 + 记录待办,未改代码。 - [x] ✅(247dc5d后端audit按id去重治本+useAiEvents.ts:205前端findToolCall幂等守卫双保险·候选A根因闭环) B-260616-21 [P1] — **aichat 工具卡片「重复读取同一文件」表象(read_file 出现两条卡:一 0 行·7.1KB running、一 183 行·7.1KB completed)**。用户报对话记录里 `读取 .../api/ai.ts` 出现两次。**根因分析**(已走查前端流转 + 后端 emit 点):①前端 `useAiEvents.ts:202-206` `AiToolCallStarted` 分支直接 `lastMsg.toolCalls.push(info)`,**无 id 幂等守卫**(对比同文件 `startToolSlowTimer:58` 有 `_toolTimers.has(callId)` 守卫,Started 漏了同款判重)②`findToolCall`(`aiShared.ts:41-49`) 按 `id` 命中**首个**返回,`AiToolCallCompleted`(`:213`) 只 update 第一张卡 → 同 id 若被 push 两次,第二张卡永远 running,`ToolCard.vue:72` `parsed?.lines||0` 无 result 时显示 `0 行` ③后端 `audit.rs:538-549` `process_tool_calls` 对 tc_list 每 draft emit 一次 Started;read_file=Low risk(`:561`) **无去重**(去重仅 High,`:553-556` 注释明确 Low 不进)。**两候选根因**:**(A 最贴合现象)** 同一 `tool_call_id` 被**重复 emit Started**(后端 `process_tool_calls` 重入 / `stream_recv.rs` 生成重复 id / `agentic.rs` loop 重 process 同轮)→ push 两张卡 → Completed 只更首张(183 行)→ 次张残留 running(0 行);**(B)** agent loop 多轮 LLM 真读两次(不同 id)→ 但应两卡皆 183 行,与「0 行 + 183 行」现象不符,**排除为主因**。**待查**:后端同 id 重复 emit 的确切路径(`audit.rs:532` tc_list 来源 `tool_calls_acc` + `stream_recv.rs` id 生成 + `agentic.rs` 是否对同轮 tool_calls 重复 process)。**修复方向**(待定):①前端 Started push 前加 `findToolCall(event.id)` 幂等守卫(确定性低风险,对齐 startToolSlowTimer 守卫,治标兜底) ②后端查重复 emit 源治本(定位 stream_recv/process/loop 哪环生重复)。**关联** F-260616-05(agentic loop 工具去重缺失,batch53 仅 High 去重,read_file/search_files 等 Low 未去重;本现象若实为候选 A 则与 F-05 不同维度——F-05 是不同 id 重复调用,本条是同 id 重复 emit)。— src/composables/ai/useAiEvents.ts(:195-209 Started 分支) + src/composables/ai/aiShared.ts(:41 findToolCall) + src/components/ToolCard.vue(:72 lines 展示) + src-tauri/src/commands/ai/audit.rs(:538-549 emit Started)。 ### 🔧 2026-06-17 定期全量走查(第1轮·仅走查·未实施) > 周期性自检(session-role-diagnose-only:不碰代码,文档不对改文档 + 代码问题记 todo)。本轮范围:secret.rs 全文 + 近 6 提交 vs todo 状态核对 + 工作区未提交改动(tauri.conf.json)。 **文档状态校正(治本/实现已落地,原 [ ] 更新为 [x])**: - B-260616-21 → ✅:后端 247dc5d(audit.rs:534-540 `seen_ids: HashSet` 按 id 去重保首个)+ 前端 useAiEvents.ts:205 `findToolCall` 幂等守卫双保险,候选 A 根因(LLM 复用同 tool_use.id 致 emit 两次)闭环。 - F-260614-04 → ✅ 后端三段(79b6a43 数据层+池选择器 / b3684f4 池 IPC+set_caps / 80c0955 stream_one_provider+候选链 fallback),前端 api/ai.ts:142 IPC 封装已接;UI 配置面板待核验。 **新登记代码问题**: - [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-06 [P3] — **secret.rs:104 注释与实现相反**。`resolve_provider_secret` 注释写「keyring 优先,fallback DB.api_key」,但代码(:105-110 `if !record.api_key.is_empty() { return DB }` + `get_provider_secret().unwrap_or_default()`)实际「**DB 优先,fallback keyring**」。与顶层设计(line 15「DB 优先,fallback keyring」)矛盾,line 104 注释为笔误。**实际无功能影响**(迁移态 DB 与 keyring 不会同时有值,set keyring→clear DB 原子序列),纯文档错误。**修法**:line 104 注释改为「DB 优先,fallback keyring」。— crates/df-storage/src/secret.rs:104 **工作区未提交改动观察(非 bug,记录供判断)**: - src-tauri/tauri.conf.json: `bundle.targets` `"all"` → `["nsis"]`(打包目标从全平台收窄到仅 NSIS Windows 安装包)。正式收窄(devflow 仅 Win 桌面)则合理;若 dev 调试临时收窄忘还原,后续 mac/linux 打包会漏。**待用户确认意图**。 ### 🔧 2026-06-17 定期全量走查(第2轮·仅走查·未实施) > 增量范围:F-05 多模态三提交(Phase2a 后端 e3cd448 / Phase2b 前端 e1d396d / Phase2c 闭环 4317d02)+ 核对 `src/components/settings/`(未跟踪)。UX-19 虚拟滚动已被 CR-62 复审 PASS(待审查.md 已回填),本轮不重审。 **F-05 多模态审查结论:质量优** - 设计周全:`content:String` 主载荷不变 + `parts:Option>` 挂载,老 JSON 无 parts → None,向后兼容零回归(audit/title/commands 读 content 当字符串全不受影响)。ContentPart `#[serde(tag="type",rename_all="snake_case")]` ↔ 前端 TS discriminated union `type:'text'|'image'`,wire 格式严格对齐。 - 转换层协议正确:Anthropic image `source.type=base64` + media_type + data;OpenAI `image_url` data URI / http url 双形态;OpenAI `content:String→serde_json::Value` 双形态,String 简写保纯文本端点兼容。 - 所有权处理到位:openai_compat 注释「须在 move m.tool_calls 之前算,借用 m」——Rust 借用易错点作者注意到了。 - truncate_parts 防撑爆:Image base64 → 占位 Text 片,url 模式原样;落库副本不污染内存真相源(ContextManager 保留原图,重发仍带图)。 - 测试覆盖:ContentPart round-trip / legacy JSON / truncate / 转换层双形态各 2+ 测试。 - FR-S1 安全边界注释到位:commands.rs 明确 ContentPart Image base64 是图片数据非 api_key,不入敏感面。 **新登记代码问题(均 P3,无 P0/P1/P2)**: - [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-07 [P3] — **F-05 变量名误导 + 兜底注释矛盾**。①conversation.rs:109 `bytes_approx = b.len()` 实为 base64 **字符数**(文案「约 N 字符」对,变量名 `bytes_approx` 错,注释「base64 字符数 ≈ 字节数*4/3」也混淆)。②openai_compat.rs Image 缺数据兜底分支 `_ => String::new()` 注释「避免发空 url 触发 400」自相矛盾——空串本身即空 url,仍触发 400。当前不可达(`image_base64` 构造器强制 `media_type:Some`),纯注释/命名瑕疵。**修法**:变量改名 `base64_len`/文案统一;兜底分支改为明确报错或移除误导注释。— crates/df-ai/src/openai_compat.rs:336 / src-tauri/src/commands/ai/conversation.rs:109 - [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-08 [P3] — **F-05 Anthropic url 模式发空 data 预留路径缺口**。anthropic_compat.rs:355 Image(url,base64:None) 时兜底发空 data + warn「commands 层应预拉 base64」,但 Phase2c commands.rs:177 透传 parts 时**无预拉 url 字节逻辑**。当前前端 Phase2b 只产 base64 模式(AiChat.vue:999 `FileReader.readAsDataURL`),路径不可达;但注释误导(声称的预拉不存在),未来加 URL 图片输入 / 历史 url 图重发会静默 Anthropic 400。**修法**:commands 层补 url→base64 预拉,或 provider 层 url 模式明确报错而非发空 data。— crates/df-ai/src/anthropic_compat.rs:355 **文档状态校正**: - **F-260614-04 UI 面板核验通过**:第1轮标 [x] 时备注「UI 面板待核验」,本轮确认 `src/components/settings/ProviderPanel.vue`(enabled toggle + weight 0-100 input + `onPoolToggle` 即时调 IPC,落库后后端 `reload_provider_caps` 重建)已实现 = F-04c 负载均衡池 UI。**注**:该文件当前**未跟踪未提交**(git status `?? src/components/settings/`),含 GeneralPanel/KnowledgePanel/ConnectionPanel/ProviderPanel 四组件(设置面板拆分重构),待提交纳入版本控制。 **已审文件清单(本轮)**:crates/df-ai-core/src/provider.rs · crates/df-ai/src/anthropic_compat.rs · crates/df-ai/src/openai_compat.rs · src-tauri/src/commands/ai/conversation.rs · src-tauri/src/commands/ai/commands.rs · src/api/ai.ts · src/api/types.ts · src/composables/ai/useAiSend.ts · src/composables/ai/useAiConversations.ts · src/stores/ai.ts · src/components/AiChat.vue(图片构建+渲染) · src/components/settings/ProviderPanel.vue **新登记 todo**:B-260617-07 · B-260617-08 ### 🔧 2026-06-17 Tasks.vue i18n 缺失(仅分析·未实施) > Tasks.vue:72 报 `Not found 'completed' key in 'zh' locale messages`。session-role-diagnose-only,仅分析+记todo。 **现象**:Tasks.vue:72 `$t(statusLabel(task.status))` → `taskStatusLabel()` → `TASK_STATUS_LABELS[status]` 返回 `'tasks.status.done'` → `$t('tasks.status.done')` 在 en/zh-CN 均未定义 → intlify fallback 到 en 仍缺失。 **根因**:`src/constants/project.ts:61-68` 定义了 `TASK_STATUS_LABELS`(值如 `'tasks.status.done'`/`'tasks.status.todo'` 等 7 个 status + `'tasks.statusFilter.done'` 等 5 个 filter + priority 相关),但 **en/zh-CN 的 tasks 相关 i18n 文件完全不存在**。6b67214 大量新增功能时未同步补 tasks i18n key。 **影响范围**(需补全的 key): | 组件 | 用到的 key 前缀 | 缺失的 key | |---|---|---| | Tasks.vue:72 | `tasks.status.*` (7个) | 全部 | | Tasks.vue:148-155 | `tasks.statusFilter.*` (5个) | 全部 | | Tasks.vue:96-103 | `tasks.priority.*` / `P0`/`P1`/`P2`/`P3` (5+4=9) | 全部 | - [x] ✅(误判销账·核验 tasks.ts 齐全) B-260617-10 [P2] — **Tasks.vue i18n 缺失**【独立核验:tasks.ts status 7 key(todo/in_progress/in_review/testing/done/blocked/cancelled)+ statusFilter 8 key 全有(zh/en 对称)。真实根因=老 DB status='completed' 残留 + taskStatusLabel `?? status` 兜底裸字符串 → $t() not found,已 commit 1cd7652 LEGACY_STATUS_MAP 防御修复】 > **2026-06-17 进展注记(1cd7652)**:提交 1cd7652 已做**数据层映射兜底**(project.ts 新增 `LEGACY_STATUS_MAP`:completed→done / review_ready→in_review / merged→done / abandoned→cancelled),`taskStatusLabel/taskStatusClass` 经映射后老 DB 脏态不再直接走 `$t()` 报 not found。**但 i18n key 补全(显示层)仍未做**——映射后 `$t('tasks.status.done')` 仍 not found(tasks.ts 不存在),用户从看到 "completed" 变成看到 "tasks.status.done" 原始 key。**B-260617-10 主问题(补全 21 个 i18n key)仍在**,1cd7652 是其前置的数据归一,待补 key 后老态自动正确显示。映射目标态(done/in_review/cancelled)均已核验在 TASK_STATUS_LABELS 7 态内 ✓。 ### 🔧 2026-06-18 走查·虚拟滚动修复链(B-260617-09 销账核对) > 核验 8abcd56/0ca5d98/02bee38 三 commit(B-260617-09 重叠修复链)。session-role-diagnose-only,独立核验代码形态非信 commit msg。 **8abcd56(方案 A·治本)✓**:卸载分支去 `height>0` 守卫,height>0 用实测 / 否则 offsetHeight / 再否则 40px 兜底(useAiVirtualScroll.ts:72-76);额外修 registerSentinel 首帧占位(:107-109 h≤0 设 40px 防 list 首渲染塌 0)。实施正确,比我原建议完善。 **0ca5d98(🔴 临时禁用未回退·P1 遗留)**:`shouldRender` 恒 `return true`(useAiVirtualScroll.ts:138),虚拟滚动裁剪**完全关闭**。注释标"临时禁用·定位后回退"但**仍在 HEAD**。三重问题:①8abcd56 已修根因,禁用多余;②裁剪关闭致 8abcd56 卸载 fallback **被绕过**(不走卸载分支);③长对话所有消息恒渲染,虚拟滚动形同关闭性能下降。**应回退**:恢复 `return renderedKeys.value.has(key) || pinnedKeys.value.has(key)`。 **02bee38 销账 B-260617-09**:根因 8abcd56 修了,0ca5d98 诊断未回退销账略早。**→ 2026-06-18 最终决策:彻底移除虚拟滚动**(见下工作区进展),非回退 0ca5d98 恢复裁剪。B-260617-09 真闭环。 - [x] ✅(2026-06-18·转向彻底移除·工作区待提交) **B-260618-01** — 原"回退 0ca5d98 恢复虚拟滚动裁剪"**→ 转向彻底移除虚拟滚动**(删 useAiVirtualScroll.ts + AiChat.vue 移除全链路,见下进展段)。理由:IO/RO 时序竞态致重叠(shouldRender 恒 true 时 IO 仍设/清 sentinel minHeight 竞态源未除)+ 消息量级不需要。B-260617-09 真闭环。 > **🔥 2026-06-18 工作区进展(方案变更·虚拟滚动整体删除)**:wf agent 未回退 0ca5d98,而是**彻底删除 useAiVirtualScroll.ts**(工作区 `D`,-175 行)+ AiChat.vue 移除全部 6 处引用(模板 ref / import / 解构 / onMounted setupVirtualScroll / lastStreamingRenderKey+watch),`grep` 确认**无残留引用**(编译安全)。重叠由此根治(移除 IO/RO 时序复杂度源)。**B-260618-01 以"删除文件"方式解决**(非回退临时禁用)。**代价**:长对话所有消息恒渲染(无虚拟化),性能下降——正确性优先权衡。**均工作区未提交**(HEAD 仍 0ca5d98),待提交入库。同期改动:ToolCard run_command 增强 + ToolCardList userCollapsedCards 修复首卡折叠 bug(见 B-260618-03)+ **U-260618 global-toggle(全部展开/收起)从 topbar 下沉到首个可折叠分组标题行尾巴**(firstCollapsibleGi computed,与 run_command 分组标题"run command N"同行,chevron 移除 margin-left:auto)+ **分组标题行加 max-width:var(--df-msg-max-width)(90%) 对齐气泡/卡片宽度**(原无 max-width 拉满 100% > 气泡/卡片 90%)+ global-toggle 移除独立按钮 padding/背景高度=文字行高不撑高标题行(见 B-260618-04)。 - [x] ✅(2026-06-18·工作区待提交·vue-tsc EXIT 0) **B-260618-03** — **ToolCardList 首卡折叠 bug**。`isCardExpanded`(ToolCardList.vue)`ci===0` 兜底 `return true` 覆盖用户折叠——多卡分组首卡默认展开,用户点折叠后 ci===0 仍 true 重新展开。**修复**:加 `userCollapsedCards` 记忆态,`isCardExpanded` 最优先判断用户折叠(覆盖 ci===0 兜底 + userExpandedCards 记忆);`toggleCardExpand` 改用 isCardExpanded 判当前真实态(避免首卡 toggle 误判反向展开);template `@toggle` 传 tc/gi/ci。— src/components/ToolCardList.vue(isCardExpanded + toggleCardExpand + userCollapsedCards) - [x] ✅(2026-06-18·工作区待提交·vue-tsc EXIT 0) **B-260618-04** — **ToolCardList 全部展开/收起下沉 + 宽度对齐(U-260618)**。①global-toggle 从 topbar 下沉到首个可折叠分组标题行尾巴(firstCollapsibleGi computed,与 run_command 分组标题同行,chevron 移除 margin-left:auto);②分组标题行加 max-width:var(--df-msg-max-width)(90%) 对齐气泡/卡片宽度(原无 max-width 拉满 100%);③global-toggle 移除独立按钮 padding/背景高度=文字行高不撑高标题行。— src/components/ToolCardList.vue(firstCollapsibleGi + template global-toggle 下沉 + CSS) - [x] ✅(2026-06-18·工作区待提交·vue-tsc EXIT 0) **B-260618-05** — **AI 独立窗口吸附挤出屏幕 + 宽度偏窄**。①吸附挤出:`syncToMain` 把 AI 窗口放主窗口右(pos.x+size.width+GAP),主窗口最大化时 AI x=屏幕宽+4 挤出屏幕。**修复**:syncToMain 增强——主窗口 isMaximized 时 unmaximize + 主+AI 宽度超屏幕时缩小主窗口(newMainWidth=screenWidth-GAP-AI_WIDTH-pos.x,最小 400px 保护)+ _isSyncing flag 防 syncToMain 改主窗口 size 触发 onResized 递归;屏幕宽度用顶层函数 `currentMonitor()`(@tauri-apps/api/window,非 Window 实例方法)。②独立窗口窄:detachPanel width 520→600 对齐吸附态 AI_DOCK_WIDTH。— src/composables/ai/useAiWindow.ts(syncToMain + detachPanel) ### 🔧 2026-06-17 aichat 消息全量重叠(5角度深入分析·未实施) > 用户报 aichat 对话「大量重叠」「不单间距问题」。session-role-diagnose-only,5 角度并行论证后记录。 > **🔥 2026-06-17 补充触发场景(展开全部·同根因)**:用户报"展开全部"折叠段(archived/compressed)后,**展开内容未把后续消息往下挤压,而是与未展开消息重叠**。机制同主根因:展开段 → `renderItems` computed 新增 msg item(key=`'e-'+seg.key+'-'+m.id`,AiChat.vue:2586)→ 新 item 注册 sentinel,`itemMeta` height 初始 0(useAiVirtualScroll.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 上"+后续连续重叠。 > - **height=0 高发窗口=第二次发送**:reply2 push → `lastStreamingRenderKey`(:2596)切到 reply2 → watch(:2604)`setPinnedMsgs` reply1 移出 pinned;同时 `isLastAi(reply1)`(:1561)变 false → bubble key(:463)从 `_mdRenderKey` 变 `0` → **Vue 重建 reply1 bubble**(流式 streamingBlocks→renderContent 切换)。pinned 移出 + bubble 重建叠加,RO/IO 在此窗口易捕获 height=0(首次挂载初始化 0 :104 / 重建中间态 / RO 异步未跟上最终值)。 > - **确诊待 DevTools**:第二次发送后查 ①reply1 的 `itemMeta.height`(useAiVirtualScroll itemMeta Map)②reply1 `.ai-msg-slot` 的 inline `minHeight` + `offsetHeight` ③`shouldRenderMsg(reply1)` 是否变 false。若 height=0 且 minHeight 未设 → 确诊。 > - **修法选项**: > - **A 治本**:useAiVirtualScroll.ts:73 卸载分支去掉 `height>0` 跳过,height=0 时 fallback 测 `meta.el.offsetHeight`(卸载前内容还在,offsetHeight 即真实高度)作 minHeight。 > - **B 兜底**:`.ai-msg-slot` 加静态 `min-height`(如 60px),防塌 0(治标,虚拟滚动 height=0 隐患仍在)。 > - **C 隔离验证(推荐先做)**:临时禁用虚拟滚动(`shouldRenderMsg` 恒 true),立判重叠是否消失 → 确诊是否虚拟滚动所致(5 分钟验证)。 **现象确认(基于截图)**: | 现象 | 判定 | |---|---| | 顶部出现 `` 等模板源码文本 | ✅ 正常 — AI 回复中 read_file 读取 AiChat.vue 源码后 v-html 展示,非渲染泄漏 | | 消息/工具卡大量重叠堆叠 | 🔴 异常 — 布局层叠问题 | | 整体挤压、间距消失 | 🔴 异常 | **已排除项**: | 检查点 | 结论 | |---|---| | CSS 声明链路 | ✅ `.ai-messages` 有 `display:flex;flex-direction:column;gap:14px`(:3109-3116),无外部覆盖 | | scoped 样式冲突 | ✅ 单 `