文档: 看板销账与登记 + 设计档六份 + 索引补录
- 看板: 907 补注/909~941 登记与销账(含 929 模型停用根因与 4 处次生缺口、931 队列丢失、933/935/937 新需求、939 上下文瓶颈清单、941 C-r0 行为变更待拍板) - 归档: 全量完成态条目/段落 + 只读核验销账 4 段迁 todo归档/2026-10-10.md(机械搬运零措辞改动,未完成项零丢失) - 设计档: 输入框列表预览与自动编号 / 定时消息与待发箱持久化(统一 ai_outbox 底座) / 对话内定时延时任务 / 消息渲染管线重构(P0-P2 分期) / 上下文分层组装器落地方案(记账基点单一真值,与并行写者职责划清) / FileTree 大目录方案(推荐后端分页,否决虚拟滚动) / u-relay 运维手册 - 索引与事实修正: docs/INDEX crate 数 8→13 + 四子树补录 25 条 + 专项设计补录 27 条;纠正「项目无独立 ARCHITECTURE.md」等过时声明;统一 bun 口径 - CLAUDE.md: 新增「命名与代码风格」节(变量/方法名精简+准确;DRY·简洁·扩展性·高内聚低耦合·单一真值来源)
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
# 消息渲染管线重构 — 设计
|
||||
|
||||
> 日期: 2026-10-10 | 状态: 设计稿(待拍板) | 勘察: 只读实测锚点(全仓零 `v-memo`,实测 `grep -c v-memo = 0`)
|
||||
> 对象: `src/components/ai/MessageList.vue`(3041→实测 **3054 行** God 组件)
|
||||
> 目标(用户原话转译): 长会话流式掉帧根治 —— 拆分 God 组件、渲染记忆化、引入 `v-memo`、去 `JSON.stringify` 指纹、流式帧降载。
|
||||
> 说明: 本专题为**只读走查 + 设计**,不含代码实施;所有行号均为实测锚点(文件以 `E:/wk-lab/devflow` 为根)。
|
||||
|
||||
---
|
||||
|
||||
## 一、现状勘察(锚点实测)
|
||||
|
||||
### 1.1 文件构成(3054 行 = script 1476 + template 460 + style 1117)
|
||||
|
||||
| 段 | 行区间 | 行数 | 备注 |
|
||||
|----|--------|------|------|
|
||||
| `<script setup>` | `1–1476` | 1476 | 逻辑 + 组合式调用 + 大量内联渲染辅助 |
|
||||
| `<template>` | `1477–1936` | 460 | 单一 `v-for` 扁平渲染 + 6 个 `v-if/v-else-if` 分支 |
|
||||
| `<style scoped>` | `1938–3054` | 1117 | 全组件样式集中于此(拆分后随子组件 scoped 自然分流) |
|
||||
|
||||
职责分类(**>6 类**,单一组件内并存):
|
||||
|
||||
1. 列表编排/分段 —— `messageSegments`(`:837`)、`renderItems`(`:1067`)、时间线 `needsTimeSep`(`:1049`);
|
||||
2. 轨迹卡/步骤 —— trace 分支(`:1510`)/step 分支(`:1587`)+ 聚合助手 `:993–1246` + 轮次导航 `traceNav`(`:1386`);
|
||||
3. 子代理完成卡 + peek 浮层 —— agentFin 分支(`:1601`)+ `openAgentTrace`(`:1280`)+ `agentPeek*`(`:1297–1384`);
|
||||
4. 消息气泡 —— msg 分支(`:1644–1784`),user 走子组件 `MessageItem.vue`,assistant/system/json/长文折叠/错误操作条内联;
|
||||
5. token 用量 —— 派生 `:172–224` + 模板块 `:1792+`;
|
||||
6. 复制/选区 —— `copyMsgContent`(`:603`)、`copyMsgAsImage`(`:640`)、`saveSelection`/`restoreSelection`(`:501/:545`)、`offsetToDOMPosition`(`:481`)、代码块复制(`:728`);
|
||||
7. 滚动/折叠联动 —— `useMessageScroll`(`:21/:230–238`)、`collapseAllToolLists`(`:395`)、`anchorCollapseScroll`(`:354`)、`buildActiveToolIds`(`:380`);
|
||||
8. 展开状态集合 5 组 —— `useToggleSet`(`:869–880`)+ `expanded*`(`:883/:891/:944/:949/:953`)。
|
||||
|
||||
流式渲染(`useStreamRenderer.ts`)与 Markdown(`useMarkdown.ts`)此前已抽出;**本专题针对剩余编排层**。
|
||||
|
||||
### 1.2 热路径触发链(谁在每帧/每次事件重算)
|
||||
|
||||
| 触发源 | 频率 | 影响 | 锚点 |
|
||||
|--------|------|------|------|
|
||||
| `streamingBlocks` 每帧更新(rAF) | **每帧** | 组件 render 函数整体重跑 → `v-for` 对全部 item **逐条新建 vnode**(全仓零 `v-memo`) | `useStreamRenderer.ts:232`(rAF 内赋值)、模板 `:1727–1728` |
|
||||
| 工具事件(status 翻转) | 每次工具事件 | `renderItems` 全量 O(n) 重算(读 `m.toolCalls`/`tc.status`/`tc.completedAt`) | `renderItems :1067`;status 就地改 `useAiToolEvents.ts:51/101/119`、`useAiApproval.ts:87/109`、`useAiConversations.ts:501`、`useAiStreamingEvents.ts:123` |
|
||||
| 指纹 watch | 每次工具事件 / 消息数变 | `map(m=>toolCalls→{id,s})` + `JSON.stringify` 全量 O(n) | **`:429–442`**(source `:430–433`) |
|
||||
| 展开/折叠 toggle | 用户点击 | `renderItems` 重算(trace/seg 集合在 dep 内)+ 全组件 render | `:1116`(expandedTraceIds)、`:1152`(expandedSegmentIds) |
|
||||
|
||||
**关键链路细节**:
|
||||
- `renderItems`(`:1067–1189`)读 `store.state.messages`(数组)、`shouldRenderMsg`(`:159–167`,读 `msg.content?.trim()`)与 `isViewingGenerating`(`:126`)。**流式 delta 期间末条 AI 的 `msg.content` 不被改写**(正文回填在收尾单点 `aiShared.ts:544`),故 `renderItems` 本身**不逐帧重算**;逐帧的开销来自 render 函数里 `v-for` 对全部 item 重建 vnode(无 `v-memo` 跳过)。工具事件则确会触发 `renderItems` 全量重算。
|
||||
- `flushBucket`(`:1075`)对每个桶 `collectTurnStats`(`useTurnTrace.ts:90`,遍历全部 toolCalls)+ trace 桶 `sumTurnTokens`(`useTurnTrace.ts:134`,遍历全部消息 token 字段)+ 模板逐条 `traceTaskRefs(item.traceMsgs)`(`:88`,遍历 toolCalls 提取任务引用)。
|
||||
|
||||
### 1.3 已有的内容级缓存(部分缓解,非本次病灶)
|
||||
|
||||
| 设施 | 锚点 | 粒度 |
|
||||
|------|------|------|
|
||||
| `renderMd` 整段缓存 LRU | `useMarkdown.ts:79`(`_MD_CACHE_LIMIT=200`)、`:195–207` | 文本→HTML |
|
||||
| 代码块装饰缓存 LRU | `MessageList.vue:681–711`(`DECO_CACHE_LIMIT=800`) | 文本→装饰 HTML |
|
||||
| 流式块级 memo + 增量切块 | `useStreamRenderer.ts:59`(`BLOCK_CACHE_LIMIT=300`)、`:67–79` | 单块→HTML |
|
||||
| `MessageItem` 派生 computed | `MessageItem.vue:99–101` | 单条 user 派生值 |
|
||||
|
||||
**缺口(本次目标)**:内容级缓存已就绪,但**列表结构级(renderItems)与 vnode 级(v-memo)缺优化** —— 前者在工具事件时全量重算,后者在每帧重建全列表 vnode。
|
||||
|
||||
---
|
||||
|
||||
## 二、目标与验收口径
|
||||
|
||||
**功能目标**
|
||||
1. `MessageList.vue` 拆分至 **单文件 <400 行**(script+template;style 随子组件 scoped 分流)。
|
||||
2. `renderItems` **记忆化**:按桶缓存,桶内容未变即复用同一 item 对象(稳定引用),不再 O(n) 重建。
|
||||
3. 引入 **`v-memo`**:流式每帧仅重渲"真正变化"的 item,其余 item 跳过 vnode 新建/补丁。
|
||||
4. 指纹 watch **去 `JSON.stringify`**,改 O(1) 版本计数。
|
||||
5. 流式帧渲染**降载**:rAF 合并(已有)+ 离屏段落跳过。
|
||||
|
||||
**验收口径**
|
||||
- 长会话(≥200 条)+ 长回复流式:每帧 patch 的 item 数从"全列表"降到"1(末条)"——Vue DevTools render 计数或 dev 打点可观测;人眼无掉帧、无卡顿。
|
||||
- 工具事件(审批/完成/拒绝)后:轨迹卡统计(`collectTurnStats`)与聚合行正确刷新;`renderItems` 重算次数 ≤ 变更桶数。
|
||||
- 展开/折叠(轨迹卡/思考 pill/JSON/长文/折叠段)即时生效,无"点了没反应"。
|
||||
- 复制/选文字保持/错误重试/子代理 peek/轮次导航跳转等既有交互逐条回归无弱化。
|
||||
- 不倒退:普通单轮问答、历史会话滚动、切换会话行为与改造前逐字节一致。
|
||||
- `vue-tsc --noEmit` 与 `pnpm test:e2e`(Playwright,`tests/e2e/smoke.spec.ts`)全绿。
|
||||
|
||||
---
|
||||
|
||||
## 三、设计方案
|
||||
|
||||
### 3.1 核心判断:三件事正交,可分笔落地
|
||||
|
||||
病灶分处三层,互不耦合,可分笔提交、独立回退:
|
||||
|
||||
- **vnode 层**(每帧) —— `v-memo` 缺 → 补 `v-memo`(不改结构)。
|
||||
- **列表结构层**(每工具事件) —— `renderItems` 全量重算 → 桶 memo 缓存(不改结构)。
|
||||
- **代码组织层**(可维护性) —— God 组件 → 拆 composable / 子组件(行为零变)。
|
||||
|
||||
设计原则:`v-memo` 与 memo 缓存**不改 `renderItems` 输出契约**(仍返回 `RenderItem[]`,`:1018–1028` 类型不变),模板 6 分支结构不变 → 回归面收敛在"渲染策略"而非"渲染语义"。
|
||||
|
||||
### 3.2 `renderItems` 记忆化(稳定 item 对象)
|
||||
|
||||
**输入指纹(桶签名 `sig`)**:仅取 `renderItems` 真正消费的字段(与 `:429` 指纹同源思路,但**按桶**):
|
||||
|
||||
```
|
||||
sig(bucket) =
|
||||
bucket.map(m => m.id + ':' + m.status + ':' + (m.toolCalls ?? [])
|
||||
.map(t => t.id + ':' + t.status + ':' + (t.completedAt ?? 0) + ':' + (t.argsHash ?? '')).join(','))
|
||||
.join('|')
|
||||
+ '#' + finalIndex // 完成态桶的 finalMsg 下标
|
||||
+ '#' + (expandedTraceIds.has(traceKey) ? 1 : 0)
|
||||
```
|
||||
|
||||
**缓存**:组件级 `Map<string, RenderItem[]>`(LRU 有界,如 128,沿用 `_decoCache:681` 同款范式)。命中即回**同一 item 对象数组**(引用稳定);未命中才 `collectTurnStats`/`sumTurnTokens`/`traceTaskRefs` 并写缓存。顶层 `items` 数组仍每次重组(桶数 ≪ 消息数,成本可忽略)。
|
||||
|
||||
**收益**:①工具事件时 O(n)→O(变更桶);②item 引用稳定 → `v-memo` 的 dep 可用 `[item]` 表达(见 3.3)。
|
||||
|
||||
**风险**:签名漏字段 → 命中陈旧 item → 界面不刷新。故签名须覆盖所有影响输出的字段;此风险由 3.3 的保守 dep 兜底(primitive dep 即使签名漏,值变仍会重渲)。
|
||||
|
||||
> 备选(更保守):不引入桶 memo,P0 仅 v-memo。但工具事件时的 O(n) 重算仍在 —— 建议两步都做、分两笔提交。
|
||||
|
||||
### 3.3 `v-memo` 依赖数组口径
|
||||
|
||||
**核心原理**:`renderItems` 是 computed,跨帧被缓存(流式帧不重算)→ item 对象跨帧引用稳定;任其**已读取**的响应式源变化 → 重算 → 新 item 对象 → `[item]` dep 变化 → 该 item 重渲。因此 **dep 只需补"`renderItems` 未读、但模板渲染该 item 时读了"的兄弟响应式源**。
|
||||
|
||||
**已读(自动失效,无需列入)**:`store.state.messages`、`m.status`/`m.role`/`m.content`(经 `shouldRenderMsg`)、`expandedTraceIds`(`:1116`)、`expandedSegmentIds`(`:1152`)、`isViewingGenerating`(读 `streaming`)。
|
||||
|
||||
**未读(必须补入 dep,否则 v-memo 跳过致不刷新)**:
|
||||
|
||||
| 源 | 影响 | 锚点 |
|
||||
|----|------|------|
|
||||
| `expandedThinkIds` | 轨迹卡内思考 pill 展开 | `:953/:1559` |
|
||||
| `expandedJsonIds` | JSON 工具结果展开 | `:944/:1711` |
|
||||
| `expandedLongIds` | 长文折叠/展开 | `:891/:898/:1714` |
|
||||
| `tokenPopoverMsgId` | token 详情面板显隐 | `:180` |
|
||||
| `editingMsgId` | user 编辑按钮显隐 | `:1691` |
|
||||
| `agentPeekLoadingId` | 子代理 peek 转圈 | `:1625` |
|
||||
| `_mdRenderKey` | marked 就绪后首屏重渲 | `:457–458/:718` |
|
||||
| 任务标题源 | trace 任务 chip 文案 | `taskTitleOf :83` |
|
||||
|
||||
**推荐写法**(DRY 单出口,补 dep 一处维护):
|
||||
|
||||
```ts
|
||||
// template: <template v-for="item in renderItems" :key="item.key" v-memo="itemDeps(item)">
|
||||
function itemDeps(it: RenderItem): unknown[] {
|
||||
return [it, expandedThinkIds.value, expandedJsonIds.value, expandedLongIds.value,
|
||||
tokenPopoverMsgId.value, editingMsgId.value, agentPeekLoadingId.value,
|
||||
mdRenderVersion.value, taskTitleVersion.value]
|
||||
}
|
||||
```
|
||||
|
||||
- `[it,...]` 依赖"renderItems 重算即新对象"这一既定事实(无需 primitive 枚举,简洁);兄弟集合用**整体引用**(`useToggleSet` 每次 toggle 替换 Set → 引用变 → 全部 item 重渲)。toggle 为用户点击(低频),全量重渲可接受,换取 dep 永不过期。
|
||||
- `taskTitleVersion`/`mdRenderVersion` 用 O(1) 版本号(见 3.4)表达异步就绪源,避免把整个 `skills`/`tasks` 数组塞进 dep。
|
||||
|
||||
**风险与兜底**:若 Vite/Vue 编译器不支持在 `<template v-for>` 上挂 `v-memo`(与内部 `v-if` 链组合),则**回退方案**=为每个 item 包一层 keyed 子组件(即 P2 的 `<AiMessageBubble :item>` 提前),`v-memo` 落在子组件根 —— 属结构性,故列为风险项而非默认路径。
|
||||
|
||||
### 3.4 指纹 watch 改 O(1) 版本计数
|
||||
|
||||
**现状**(`:429–442`):source(`:430–433`)每次工具状态变动作 `messages.map(...)` + `JSON.stringify`,O(n) 分配 + 序列化。
|
||||
|
||||
**改法**:引入 `toolEpoch`(数字 ref,建议落 `aiShared.ts` 与 `convStreamStates` 同层),在**唯一入口** `bumpToolEpoch()` 自增;所有工具状态变更点调用:
|
||||
|
||||
- `useAiToolEvents.ts:39`(push 新 toolCall)、`:51`(completed)、`:101`(pending_approval)、`:119`(rejected);
|
||||
- `useAiApproval.ts:87`(running/rejected)、`:109`(completed);
|
||||
- `useAiConversations.ts:501`(待审批回填)、`useAiStreamingEvents.ts:123`(待审批)。
|
||||
|
||||
watch 改:
|
||||
|
||||
```ts
|
||||
watch([() => store.state.messages.length, () => toolEpoch.value], () => {
|
||||
if (isFollowingBottom.value) collapseAllToolLists(buildActiveToolIds(store.state.messages))
|
||||
})
|
||||
```
|
||||
|
||||
**收益**:每次工具事件从 O(n) map+stringify 降为 O(1) 计数;`lastMsgSnapshot` 去重串可删(watch 已按值变化触发)。**代价**:需在 ~7 处 mutation 点补 `bumpToolEpoch()` —— 建议收敛到一个 `markToolChanged()` 助手,避免散点漏调(漏调 = 折叠联动不触发,非渲染错误,风险低)。
|
||||
|
||||
### 3.5 流式帧渲染降载
|
||||
|
||||
1. **rAF 合并** —— 已有(`useStreamRenderer.scheduleStreamParse:225`),每帧仅一次 parse;保持不动。
|
||||
2. **v-memo**(3.3)—— 每帧仅末条 item 重建 vnode,是**最主要的降载**(治"逐帧全列表 vnode 重建")。
|
||||
3. **离屏段落跳过** —— 对 `.ai-msg-slot` 加 CSS `content-visibility: auto` + `contain-intrinsic-size`(浏览器级跳过离屏布局/绘制,零 JS)。**风险**:与 `scrollToBottom`(`anchorCollapseScroll:354` 读 `getBoundingClientRect`)可能因离屏高度估算产生滚动抖动 → 建议 P1 引入并实测,回退=去该 CSS 类。
|
||||
4. **renderItems 不读流式正文** —— 现状已满足(正文回填在 `aiShared.ts:544` 收尾单点,delta 期间不改 `msg.content`);设计上**明确此约束不得破坏**(勿在 `renderItems` 内引入 `store.state.currentText` 读取),否则 computed 每 delta 重算,前功尽弃。
|
||||
|
||||
### 3.6 拆分方案(职责切分 + 数据流)
|
||||
|
||||
**数据流不变**:`store.state.messages`(真相)→ `messageSegments` → `renderItems`(`RenderItem[]`)→ 模板分支渲染。拆分仅重排"谁来持有逻辑"。
|
||||
|
||||
**P1 — 抽出 composable(模板不动,纯脚本迁移 + 依赖注入)**:参照已抽的 `useStreamRenderer`(回调注入)范式:
|
||||
|
||||
| 新 composable | 迁出内容 | 入参(注入) | 出参 |
|
||||
|---------------|----------|-----------|------|
|
||||
| `useRenderItems` | `messageSegments`/`renderItems`/时间线/traceNav + 桶 memo | messages、`isViewingGenerating`、各 expanded 集合 | `renderItems`、`traceNav`、`jumpToTrace` |
|
||||
| `useTracePanel` | trace 展开/task refs/轮次导航 + agent peek 浮层 | store、`expandedTraceIds` | `toggleTrace`、`traceTaskRefs`、`openAgentTrace`/`closeAgentTrace`、`agentPeek*` |
|
||||
| `useMessageActions` | 复制(文本/图片)/重试/装 obscura/去设置 | store、`emit`、`scrollToBottom` | `copyMsgContent`、`handleRetry`、… |
|
||||
| `useTextSelection` | 保存/恢复选区 + 偏移定位 + 代码块复制委托 | `messagesContainer` | `saveSelection`/`restoreSelection`、`onMdCodeCopyClick` |
|
||||
| `useTokenUsage` | token 派生 + popover 状态 | — | `token*Of`、`tokenPopoverMsgId`、`toggleTokenPopover` |
|
||||
|
||||
**P2 — 抽子组件(模板分流 + style scoped 随迁)**:
|
||||
|
||||
| 子组件 | 迁出模板 | 目标 |
|
||||
|--------|----------|------|
|
||||
| `TraceCard.vue` | trace(`:1510–1583`)+ step(`:1587–1596`)同构两态 + rail 面板 | 含内部 `v-memo`/computed,思考 pill 展开自治 |
|
||||
| `AgentFinishedCard.vue` | agentFin(`:1601–1627`) | peek 点击 emit 上抛 |
|
||||
| `AiMessageBubble.vue` | msg 的 assistant/system/json/长文折叠/错误操作(`:1697–1784`)+ 流式块(`:1727–1730`) | 单条气泡自治,`item` 作 prop |
|
||||
| `TimeSeparator.vue` / `SegmentSeparator.vue` | timesep(`:1629`)/sep(`:1650–1671`) | 小件,可留 MessageList |
|
||||
|
||||
拆完后 `MessageList.vue` 余:容器 + 滚动 + 编排 `v-for` + 事件委托(目标 <400 行)。
|
||||
|
||||
**高内聚低耦合要点**:子组件只收 `item` 数据 + 经 `emit` 上抛动作(不直接持 `store` 写);`ToolCardList`/`MessageItem` 已是此模式,新组件对齐。
|
||||
|
||||
---
|
||||
|
||||
## 四、分期(每期可独立上线、可回退)
|
||||
|
||||
### P0 — 零风险渲染层(v-memo + 桶 memo + 版本计数)**不改结构**
|
||||
- 补 `v-memo`(`itemDeps`,3.3);`renderItems` 桶 memo(3.2);指纹 watch 改 `toolEpoch`(3.4)。
|
||||
- **不改**模板分支、DOM 契约(`data-msg-id`/role class)、`RenderItem` 类型。
|
||||
- 建议拆 **两笔**提交:`v-memo` 一笔、桶 memo+版本计数一笔 → 可分笔 revert。
|
||||
- 验收:§二"每帧仅末条 patch"人眼/打点;工具事件统计刷新正确;toggle 即时生效;`vue-tsc` + e2e 全绿。
|
||||
|
||||
### P1 — 拆 composable + 离屏降载(模板不动)
|
||||
- 抽 `useRenderItems`/`useTracePanel`/`useMessageActions`/`useTextSelection`/`useTokenUsage`(3.6);`.ai-msg-slot` 加 `content-visibility`(3.5-3)。
|
||||
- 验收:`MessageList.vue` script 显著下降;所有交互回归;滚动锚点无抖动;e2e 全绿。
|
||||
|
||||
### P2 — 拆子组件(结构 + style 分流)
|
||||
- 抽 `TraceCard`/`AgentFinishedCard`/`AiMessageBubble`(3.6);style scoped 随迁。
|
||||
- 验收:单文件 **<400 行**;全交互 + e2e 全绿;`v-memo` 下沉各子组件(可改用 `[item]` 简化)。
|
||||
|
||||
---
|
||||
|
||||
## 五、影响面(五维)
|
||||
|
||||
- **兼容性**:纯前端渲染层重构,不改 IPC / 事件 / DB / 后端协议;`RenderItem[]` 输出契约与模板分支不变;DOM 语义属性保留 → 既有 e2e 选择器/`getBoundingClientRect` 锚点不破坏;`MessageItem`/`ToolCardList` 对外 props/emit 不变。
|
||||
- **破坏性**:无不可逆操作(纯代码,git 可回退)。**行为风险**=`v-memo` dep 遗漏(局部不刷新,可见回归)、桶 memo 签名冲突(串内容)。缓解:dep 保守(3.3)+ 签名含 `id` 唯一性 + LRU 有界。
|
||||
- **安全面**:无新端点/命令执行面;渲染 HTML 仍走既有 `DOMPurify`(`useMarkdown.renderMd`/`parseBlockNoCache`)链,`v-memo` 只跳过补丁、不改变 `v-html` 内容来源,无新 XSS 面。
|
||||
- **资源面**:内存 —— 新增桶 memo Map + dep 数组,有界 LRU(128);CPU —— 每帧 vnode 从 O(全列表) 降 O(1~少数);工具事件 O(n) 重算 → O(变更桶);`content-visibility` 降离屏布局/绘制。工具事件 O(1) 版本计数替代 O(n) stringify。
|
||||
- **回退开关**:`v-memo`/结构拆分属**编译期**,无运行时开关 → 回退粒度=**逐笔 git revert**(故 P0 拆两笔、P1/P2 各自独立)。`toolEpoch` 为本地计数,回退即恢复旧 watch。`content-visibility` 可用 CSS 类开关运行时回退。
|
||||
|
||||
---
|
||||
|
||||
## 六、拍板点清单
|
||||
|
||||
| # | 决策 | 选项 | 建议 |
|
||||
|---|------|------|------|
|
||||
| 1 | `v-memo` 依赖口径 | A 逐 kind primitive 枚举;B `[item, ...兄弟引用]`(依赖 renderItems 缓存保证 item 稳定);C 仅对 msg 分支上 v-memo | **B**。简洁且覆盖充分;前提=3.2 桶 memo 与 3.3 的 sibling 清单正确;A 作理论最保守备选(枚举成本高、易漏) |
|
||||
| 2 | P0 是否同时做桶 memo | A 仅 v-memo(结构零改,工具事件仍 O(n));B v-memo + 桶 memo(拆两笔) | **B**。两者正交、各自可测;桶 memo 治"工具事件级"重算,仅 v-memo 治不了 |
|
||||
| 3 | 离屏 `content-visibility` 时机 | A P0 一起;B P1 引入;C 不做 | **B**。收益大但需实测滚动锚点抖动,单列为 P1 可回退项,风险隔离 |
|
||||
| 4 | P2 子组件边界 | A 抽 TraceCard/AgentFinishedCard/AiMessageBubble;小件(时间线/折叠条)留父;B 仅抽最大件 AiMessageBubble | **A**。目标是 <400 行,需 style scoped 随迁才达标;B 达标不足 |
|
||||
|
||||
---
|
||||
|
||||
## 附:实施纪律
|
||||
|
||||
- `renderItems` 是全仓最热路径,**回归面大** → 遵循"优先级 Bug→重构→新功能、单次 1-2 文件、原子操作"策略(`memory large-refactor-strategy`);每期独立提交 + 独立 e2e。
|
||||
- 改代码一律用 Edit 工具(禁 sed/perl);验证阶段收官统一跑 `vue-tsc` + e2e,不逐文件全量 build(`CLAUDE.md` 编排纪律)。
|
||||
- 存疑处**源码形态优先于文档描述**;`v-memo` dep 清单以本设计 3.3 表为准,实施时逐条 grep 模板引用核对,勿凭空增删。
|
||||
Reference in New Issue
Block a user