Show bounded operation summaries for Continue, DeepSeek Harness and direct-model tools, retaining input details across incremental updates. Use one canonical workspace path for DeepSeek Host startup and ACP sessions so equivalent Windows paths reuse a working Runtime. Compare persisted subagent tool fields by value, release terminal task caches, and avoid rebuilding caches for duplicate remote events. Upgrade to schema 35 to recompact repeated schema-34 tool blocks while preserving events and provenance. Include regression coverage, validation records and temporary-resource test exclusions. Validation: 4155 tests passed, 66 skipped; typecheck and lint passed. Full desktop real-model validation covered all three tool-summary paths and both Windows path spellings. Storage validation evidence includes local and remote tool workloads and real database upgrade checks. Release note: Tool records now show paths, commands and search details across runtimes. Fix DeepSeek Windows workspace failures and repeated subagent history growth; schema 35 reclaims redundant history data while preserving execution records, and older clients cannot reopen the upgraded database.
171 lines
9.9 KiB
Markdown
171 lines
9.9 KiB
Markdown
# 复发缺陷与防复发规则
|
||
|
||
本文档收录已经在本仓库重复出现过的缺陷类型。每条规则都来自至少一次真实修正,不是预设的
|
||
通用最佳实践。新增条目前,先确认该问题在提交历史中确实出现过一次以上,或一次修正中同时
|
||
出现在多处。
|
||
|
||
修正复发缺陷时,优先建立一条能覆盖整类问题的机制(共享选择器、共享函数、能发现全部实例
|
||
的测试),而不是逐个点名修补。逐个修补正是这些条目反复出现的原因。
|
||
|
||
## 整窗遮罩与原生拖动区域
|
||
|
||
**症状**:弹窗覆盖标题栏区域时,其关闭按钮无法悬停和点击。
|
||
|
||
**原因**:`.brand` 与 `.topbar` 声明了 `-webkit-app-region: drag`。整窗遮罩绘制在其上方
|
||
时,原生标题栏仍然截获指针。
|
||
|
||
**规则**:整窗遮罩必须 `inset: 0` 铺满窗口,并由 `styles.css` 中统一的遮罩选择器声明
|
||
`-webkit-app-region: no-drag`。新增遮罩时扩展该共享选择器;不要在各自规则里复制
|
||
`no-drag`,也不要用 `inset: 38px 0 0` 之类的偏移绕开标题栏。完整要求见
|
||
[UI 设计系统](../../UI-DESIGN.md)。
|
||
|
||
**防复发**:`WorkspacePrimitives.test.tsx` 的
|
||
"releases the native drag region for every full-window overlay" 从 `styles.css` 自动发现
|
||
全部 `position: fixed` 遮罩并逐个校验,新增遗漏会直接失败。
|
||
|
||
**历史**:`e9679fe`、`ca4a58c` 各修了一部分被人工点到的弹窗,同一批改动又新增了两个未覆盖
|
||
的遮罩;后续复查改为共享规则加自动发现测试。
|
||
|
||
## 网页浮层遗漏原生浏览器视图
|
||
|
||
**症状**:知识库实例、项目设置等 Modal 的右侧被浏览器网页覆盖,通知或菜单也可能
|
||
被压在原生视图下面。
|
||
|
||
**原因**:`WebContentsView` 不参与 Renderer 的 CSS 层叠。先前只在关闭终端确认时
|
||
显式隐藏 viewport,没有覆盖其他页面的 Portal 和浮层。
|
||
|
||
**规则**:`BrowserViewport` 共用 `browser-viewport-occlusion.ts`,按 Modal 语义和
|
||
浮层相交状态释放显示租约,不销毁网页会话。不得再增加逐个弹窗的特殊隐藏条件。
|
||
|
||
**防复发**:组件回归覆盖 body Modal、嵌套 Modal、已打开 Modal、隐藏或非相交浮层、
|
||
通知、终端确认和非活动页签;实际 Electron 验证同时检查原生视图可见性,不能只看 DOM
|
||
的遮罩或截图。
|
||
|
||
**历史**:先前浏览器多实例修正只覆盖了终端关闭确认;2026-09-13 用户在知识库实例
|
||
Modal 再次复现,同一检查确认其他应用级 Modal 均缺少处理。
|
||
|
||
## Flex 滚动内容压缩页签和分段控件
|
||
|
||
**症状**:知识库来源选择被长参数表单压成窄缝,标签和按钮文字被裁掉。
|
||
|
||
**规则**:`PageTabs` 与 `SegmentedControl` 在共享样式上固定 `flex: 0 0 auto`;
|
||
直接包装层也必须保留高度。不要按页面复制高度常量。
|
||
|
||
**防复发**:`WorkspacePrimitives.test.tsx` 检查共享分段控件不收缩,已有平台功能、
|
||
能力和 MCP 页签回归继续保留;Electron 复验必须包含长表单与短窗口。
|
||
|
||
**历史**:设置页签曾补过包装层不收缩规则;2026-09-13 知识库创建类型的共享
|
||
`SegmentedControl` 又出现同类问题。
|
||
|
||
## 逐行查询代替主键查询
|
||
|
||
**症状**:列表渲染或校验函数随数据量线性变慢,且慢在单次操作内重复发生。
|
||
|
||
**原因**:用 `listAll().some(...)` 或 `listAll().find(...)` 判断单行是否存在。表有主键或
|
||
唯一索引时,这是把 O(1) 查询写成了全表扫描加 `JSON.parse`。
|
||
|
||
**规则**:判断单行存在或取单行数据时,在 store 上提供按键查询方法(例如 `hasBinding`、
|
||
`getBinding`),不要在调用方过滤全量列表。确实需要按分组统计时,循环外取一次并构建
|
||
`Map`,不要在循环内反复查询。
|
||
|
||
**历史**:外部知识库绑定同时出现三处:`requireLibrary`(30+ 调用点,一处在循环内)、
|
||
`getKnowledgeSnapshot`(每个知识库两次全表扫描)、`listInstances`(每个实例过滤一次)。
|
||
|
||
## 将累计过程当作增量事件反复落库
|
||
|
||
**症状**:聊天正文很小,执行记录数据库却增长到几十 GiB;只增加几个字节的子任务输出,
|
||
又写入接近 1 MiB 的旧过程副本。
|
||
|
||
**原因**:本机与远程 OpenCode 共用完整进度快照,两个桌面落库入口都按流式频率新增
|
||
完整 JSON;正文流的缓冲器没有覆盖这种累计快照。
|
||
|
||
**规则**:界面快照不直接作为追加式日志的存储表示。子任务事件通过共享存储层记录变化,
|
||
保留逐条还原和远程来源去重;会话仍保存完整显示结果。旧用户数据通过同一编码器无损
|
||
转换后回收空间,不靠删聊天或单独修本机数据库解决。
|
||
|
||
**防复发**:使用含大工具输出的多次更新检查总存储量,并验证重启、重复远程事件、
|
||
消息事务回滚及旧库中断续跑。工具对象须包含生产路径中的 `undefined` 可选字段,
|
||
经过真实 JSON 往返后仍识别为无变化,不能按对象键存在性比较。真实模型验证须包含
|
||
子代理正常使用工具、修改文件及运行测试,不能只测禁用工具的文本流。
|
||
真实旧库需验证逐条等价和物理文件缩小,已升级 schema 的重复数据同样要覆盖。
|
||
|
||
**历史**:2026-09-13 检查到 305,551 条子任务事件占用 90.11 GiB;
|
||
本机 `appendTaskEvent` 与远程 `insertRemoteTaskEventOnce` 同时存在重复保存路径。
|
||
0.13.1 发布后的真实工具任务再次复现:同批 928 条事件有 7,787 次重复工具块写入;
|
||
只统一 JSON 空字段后,事件负载从 10,467,622 降为 452,391 字节,逐条还原一致。
|
||
|
||
## 生产路径复用“测试”函数
|
||
|
||
**症状**:正常用户操作触发了只应在配置校验时出现的错误码,或多发了一次网络请求。
|
||
|
||
**原因**:让生产路径直接调用为“测试连接”“保存前校验”准备的函数。这类函数通常带有额外的
|
||
能力预检和更严格的失败条件。
|
||
|
||
**规则**:生产路径与校验路径各自提供入口。校验入口可以在生产入口之上叠加预检,反向复用
|
||
不允许。共用的裁剪、归一化逻辑抽成独立函数由两者调用。
|
||
|
||
**历史**:对话检索曾调用 `ExternalKnowledgeService.testRetrieval` 并把用户问题放进
|
||
`testQuery`,导致每次提问多一次 detail 请求,且 `EXTERNAL_KB_CAPABILITY_UNAVAILABLE`
|
||
会出现在正常对话中。
|
||
|
||
## 事件迭代结束前释放底层订阅
|
||
|
||
**症状**:并行 OpenCode 会话已结束,但部分 SSE 响应流仍未关闭;只检查最终取消信号
|
||
会误判回收成功。
|
||
|
||
**原因**:OpenCode SDK `1.18.29` 在迭代器 `return()` 时移除取消监听并释放 reader lock,
|
||
但不主动取消 reader。外层 `for await` 先结束 SDK 迭代器,外层 `finally` 再 abort 的
|
||
顺序过晚,带缓冲的响应流可能继续存活。聊天和原生 Compact 均曾使用该顺序。
|
||
|
||
**规则**:两条 SDK 消费路径共用 `consumeEventSubscription`,先 abort 对应订阅,再结束
|
||
底层迭代器;请求取消和 Server 生命周期仍保留各自作用域。不要修改依赖包、放宽回收
|
||
断言或关闭共享 Server 来代替单次订阅回收。
|
||
|
||
**防复发**:Runtime 回归检查正常结束和消费方提前返回时,底层迭代器清理前取消已经发生。
|
||
`opencode-runtime-lifecycle.test.ts` 使用真实二进制与 SDK,在 1、4、8 路并行、单请求
|
||
取消和原生 Compact 后检查打开的响应流归零,并验证并行请求不被误取消。
|
||
|
||
**历史**:`b03c256` 已增加请求级订阅取消;2026-09-13 候选检查仍复现 3 条响应流未关闭,
|
||
复查发现聊天与 Compact 都需要调整上述顺序。
|
||
|
||
## 快照比对代替取消
|
||
|
||
**症状**:为检测“操作期间配置被改动”而反复读回状态并 `JSON.stringify` 比较。
|
||
|
||
**原因**:把取消语义当成配置漂移检测来实现。桌面单用户场景下,改动配置的那次操作本身就能
|
||
取消在飞请求。
|
||
|
||
**规则**:用 `AbortController` 在配置变更点取消在飞请求,并让被取消的操作不写回任何结果。
|
||
不要引入前后快照比较、版本化内部凭据或多阶段状态机。唯一性约束交给数据库索引。
|
||
|
||
**历史**:外部知识库服务曾有 5 处快照比对与专用错误码 `EXTERNAL_KB_CONFIG_CHANGED`;全部
|
||
删除后行为反而更正确,因为旧实现会把“用户停用实例”记成 `probeStatus: 'failed'`。
|
||
|
||
## 删除疑似死代码时的验证顺序
|
||
|
||
**症状**:删除后类型检查通过,但运行时行为改变,或测试断言的是已不存在的行为。
|
||
|
||
**规则**:删除枚举取值、文案键、导出函数前,按此顺序确认:
|
||
|
||
1. `rg` 全仓搜索引用,区分生产代码与测试 fixture。
|
||
2. 确认该取值是否可能由生产代码写入。仅测试 fixture 使用即为死代码。
|
||
3. 检查是否属于已持久化数据的解析契约。持久化契约即使当前不写入也必须保留。
|
||
4. 删除后跑受影响的定向测试,再跑 `npm test`。
|
||
|
||
**历史**:`probeStatus` 的 `'healthy'` 仅存在于测试 fixture,可以删除;而
|
||
`externalKnowledgeLocatorSchema` 的 `sourceUrl` 虽然同样从不写入,但它解析已持久化的引用
|
||
数据,必须保留。两者表面症状相同,处置相反。
|
||
|
||
## 渲染期派生值未记忆化
|
||
|
||
**症状**:流式响应期间界面卡顿,或大列表操作延迟。
|
||
|
||
**原因**:在组件体内直接构建 `Map`/`Set` 或链式 `filter`/`flatMap`,而该组件在流式增量期间
|
||
每个 token 都会重渲染。
|
||
|
||
**规则**:组件体内的派生集合必须包在 `useMemo` 中,依赖收窄到真正影响结果的值。紧邻的
|
||
`useMemo` 之后又裸建集合,会让记忆化收益减半。
|
||
|
||
**注意**:React 允许在渲染期用守卫调用 `setState` 来响应 props 变化,这是官方推荐做法,不要
|
||
把它改写成 `useEffect`。仓库 lint 规则 `react-hooks/set-state-in-effect` 会拒绝后者。
|