Skip to content

Bug Fix Knowledge Wiki

Yao Jingxi edited this page Sep 12, 2026 · 1 revision

Lithe Bug-Fix Knowledge Wiki

这是一份从 Lithe-IDEA 的 GitHub Pull Request 中整理出来的工程知识库。它关注的不是“改了哪些文件”,而是:

什么输入或时序触发了问题?为什么旧模型会允许它发生?修复建立了什么不变量?如何证明没有把问题换成另一个问题?

范围与阅读方式

截至 2026-09-12,检索了仓库 GitHub PR 列表、PR 描述、提交标题、改动文件和 PR 中记录的验证结果,覆盖早期 PR 到 #643。首版重点纳入有明确症状、根因、解决方案或回归证据的 bug、性能、可靠性和 CI 修复;纯发布同步、纯文案、单纯功能建设和没有足够根因证据的视觉调整不在正文展开。

同一个问题的重复迭代按“问题族”理解,而不是把每个 PR 当成完全独立的 bug。例如关闭的 #111、#169、#173、#195、#203 分别由后续合并的 #112、#172、#174、#197、#209 继承或重做;正文会优先引用最终合并版本,必要时保留前序 PR 作为历史线索。

PR 链接是原始证据入口;“验证”一栏是 PR 作者报告的验证,不等同于本次对整个产品的重新构建。涉及跨平台或实机环境时,明确保留 PR 自己写出的未覆盖边界。

分类导航

分类 适合回答的问题 页面
Git 与工作区状态 外部变化、多仓库、Git 操作和异步刷新为什么会丢状态或串状态? [[Git 与工作区状态
LSP、解析与边界 路径、协议、AST、Maven/Spring/JDTLS 为什么在边界输入下失败? [[LSP、解析与边界
性能与渲染 为什么滚动、输入、拖拽或大文件会卡?如何缩小失效范围? [[性能与渲染
原生 UI 与输入 AppKit、SwiftUI、Metal、TextKit、WebView2 的事件/布局语义如何对齐? [[原生 UI 与输入
CI、进程与发布 为什么测试会挂、更新会卡、发布产物会缺?如何做有界和可诊断的系统? [[CI、进程与发布

先读这 10 个案例

PR 一句话根因 可迁移的解法
#53 watcher 只看工作区且把 Git 元数据过滤掉,刷新期间事件还会丢失 先解析真实 Git watch context,再分类事件,用 pending/freeze 状态机补偿突发事件
#375 已完成的 shutdown task 仍挂在状态上,MainActor 忙等阻止清理 串行化生命周期、完成即清除 task、用 generation/pending guard 隔离旧项目
#625 Windows canonical path 的 //?/ 身份在 Core 与前端之间不一致 在边界层规范化并写入契约;前端保留防御性归一化
#626 深层 Tree-sitter AST 的递归 DFS 耗尽宿主线程栈 把递归控制状态移到堆上的显式栈,保留确定性遍历顺序
#606 空结果、失败和“不是 Git 仓库”被压成同一种状态,覆盖了最后一次有效数据 区分 empty/error,保留 last-known-good,错误按作用域展示并提供 retry
#260 LSP initialize 完成不代表 JDTLS 的 Maven import 已完成 把协议 ready 与业务 ready 分成两个状态,按业务 ready 发送 didOpen/watch 更新
#621 每次编辑/滚动都遍历全文、重排全文、查询屏幕外折叠 视口缓存、区间失效、过期结果丢弃;让状态留在局部原生容器
#599 终端缓存的 focus 和 AppKit 实际 first responder 不一致 以窗口真实焦点为唯一事实源,渲染/闪烁/输入全部从同一状态派生
#284 同步等待、管道 EOF、SIGPIPE 和后代进程让测试无法收尾 异步 drain + deadline + TERM→KILL + no-SIGPIPE + 独立进程隔离
#390 分隔条 @State 在业务父视图中逐帧变化,触发整个工具窗口重建 使用 LitheSplitPaneView 下沉尺寸状态,合并事件,保持行模型 Equatable

反复出现的根因模式

1. 事件发生了,但观察模型没有表达它

Git 元数据变化、revert 只改变历史、窗口焦点变化、服务 ready 都不是普通“文件内容变化”。如果状态模型只有一个粗粒度的 refresh 或布尔值,系统就会漏掉事件、重复刷新或把不同状态混在一起。#53、#593、#599、#260 都是同一类问题的不同表现。

2. 异步结果没有身份

取消一个任务不代表它已经停止;旧请求可能已经在回调队列里。#167、#375、#597、#606、#621 和 #39 都通过 generation、version、request identity、workspace identity 或显式 stale-result 检查来阻止旧结果覆盖新状态。

3. 边界层把“等价”误当成“相同”

@Bean 与 @BeanFactory、@GetMapping 与 @GetMappingCustom、Windows 普通路径与 verbatim path、LSP initialized 与 JDTLS imported 都不能靠前缀、字符串相等或单个布尔值判断。正确修复通常是定义规范化函数、精确 token/范围、结构化状态或共享契约。

4. 无界工作会把正常输入变成故障

递归深度、全文高亮、全文同步、无限 semaphore、无限等 EOF、未限制的下载和“等待进程退出”都可以在小概率输入上变成崩溃或卡死。Lithe 的有效解法包括显式上限、可取消、进度感知的 deadline、视口化和降级策略。

5. 视图树是状态系统的一部分

SwiftUI/AppKit/React 中“把一个值写在父视图”会决定失效范围;一个不稳定的 ref、闭包或类型擦除也会改变重建行为。#390、#511、#621、#221、#563 说明性能和正确性常常不是两个问题:稳定 identity 本身就是行为契约。

给 AI 的代码审查模板

遇到类似 PR 或新 bug,可以按下面顺序提问:

  1. 触发条件是输入边界、生命周期交错、跨线程顺序,还是仅仅某个渲染状态?能否写成最小确定性复现?
  2. 哪一个状态被错误地当成了另一个状态?例如 empty/error、initialized/ready、plain/verbatim path、current/stale。
  3. 异步任务、回调、进程和观察者的所有权是谁?取消后是否仍可能回写?完成或销毁时谁负责清理?
  4. 这次更新的失效范围是单行、单个滚动容器、一个 feature model,还是整个页面?高频事件是否触发父视图重建?
  5. Core、Swift、TypeScript、Tauri 之间是否使用同一字段、路径、退出码和错误语义?是否需要更新 schema、fixture 和另一端消费者?
  6. 回归测试是否验证了旧实现会失败,而不只是新实现会通过?是否包含取消、逆序完成、空值、重复事件、窗口/工作区切换和失败恢复?
  7. 验证报告是否区分了已执行、CI 执行、实机未执行和与本 PR 无关的基线失败?

延伸索引

以下 PR 也值得作为后续专题加入:#167(侧栏切换取消与代次)、#174(文件打开时的 SwiftUI/高亮/Java 分析)、#278(按服务隔离工具链)、#280(移除不稳定 Java inlay hints)、#305(运行配置不应等待 Spring 全量索引)、#308(故障功能的显式禁用)、#368(合并冲突造成 UI 回退)、#418(保持 Tauri 边界)、#430(更新安装生命周期)、#477(Maven wrapper 与本地仓库)、#487(Windows 标题栏拖拽)、#511(原生 Git 滚动面)、#543(分隔条 hit-test 链路)、#563(Monaco editor identity)、#568(提交文件树布局)、#584(React 回调参数)、#596(JDTLS Maven import timeout)、#597(按路径排队的暂存)、#603(LSP 生成产物隐藏规则)。

Clone this wiki locally