-
Notifications
You must be signed in to change notification settings - Fork 151
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、进程与发布 |
| 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 |
Git 元数据变化、revert 只改变历史、窗口焦点变化、服务 ready 都不是普通“文件内容变化”。如果状态模型只有一个粗粒度的 refresh 或布尔值,系统就会漏掉事件、重复刷新或把不同状态混在一起。#53、#593、#599、#260 都是同一类问题的不同表现。
取消一个任务不代表它已经停止;旧请求可能已经在回调队列里。#167、#375、#597、#606、#621 和 #39 都通过 generation、version、request identity、workspace identity 或显式 stale-result 检查来阻止旧结果覆盖新状态。
@Bean 与 @BeanFactory、@GetMapping 与 @GetMappingCustom、Windows 普通路径与 verbatim path、LSP initialized 与 JDTLS imported 都不能靠前缀、字符串相等或单个布尔值判断。正确修复通常是定义规范化函数、精确 token/范围、结构化状态或共享契约。
递归深度、全文高亮、全文同步、无限 semaphore、无限等 EOF、未限制的下载和“等待进程退出”都可以在小概率输入上变成崩溃或卡死。Lithe 的有效解法包括显式上限、可取消、进度感知的 deadline、视口化和降级策略。
SwiftUI/AppKit/React 中“把一个值写在父视图”会决定失效范围;一个不稳定的 ref、闭包或类型擦除也会改变重建行为。#390、#511、#621、#221、#563 说明性能和正确性常常不是两个问题:稳定 identity 本身就是行为契约。
遇到类似 PR 或新 bug,可以按下面顺序提问:
- 触发条件是输入边界、生命周期交错、跨线程顺序,还是仅仅某个渲染状态?能否写成最小确定性复现?
- 哪一个状态被错误地当成了另一个状态?例如 empty/error、initialized/ready、plain/verbatim path、current/stale。
- 异步任务、回调、进程和观察者的所有权是谁?取消后是否仍可能回写?完成或销毁时谁负责清理?
- 这次更新的失效范围是单行、单个滚动容器、一个 feature model,还是整个页面?高频事件是否触发父视图重建?
- Core、Swift、TypeScript、Tauri 之间是否使用同一字段、路径、退出码和错误语义?是否需要更新 schema、fixture 和另一端消费者?
- 回归测试是否验证了旧实现会失败,而不只是新实现会通过?是否包含取消、逆序完成、空值、重复事件、窗口/工作区切换和失败恢复?
- 验证报告是否区分了已执行、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 生成产物隐藏规则)。