Web diff cards compare contextual content
Web diff 卡片比较含上下文的内容
Filesystem result metadata carries before/after fragments that include unchanged context. Treating each complete fragment as removed or added mislabels shared lines and inflates both card and collapsed-row totals.
English
Problem
Filesystem result metadata carries before/after fragments that include unchanged context. Treating each complete fragment as removed or added mislabels shared lines and inflates both card and collapsed-row totals.
Decision
The Web primitive derives line patches with the maintained diff library, using maxEditLength: 256. Each exact change includes up to three neutral context lines on either side; distant changes use separate hunks and shared context contributes to neither total. Beyond 256 additions/deletions per fragment, search stops and the complete old/new fragments render as a coarse replacement, including shared lines in the display, copy, and counts. The card and diffTotals use this same deterministic derivation. This remains Client presentation under the tool presentation ownership decision, without changing persisted metadata or public props.
Alternatives considered
Unbounded comparison stalls collapsed summaries on heavily changed fragments. A deterministic edit-distance limit preserves exact sparse edits regardless of file length; a wall-clock timeout could make the summary and body choose different results under load. A coarse replacement sacrifices alignment above the limit while retaining every input line. Caching or asynchronous rendering adds ownership and invalidation work that the bounded comparison does not require. Extending durable metadata or maintaining a custom diff algorithm is unnecessary for this presentation behavior.
Measurement
A local CPU diagnostic bundled the production DiffBlock.tsx entry with esbuild (--bundle --platform=node --format=esm) and timed diffTotals under Node 26.5.0 on macOS ARM64. Each fragment has 10,000 lines: unique indexed lines replaced completely, 100 evenly spaced replacements, or alternating repeated old/shared versus new/shared lines. Input construction and module loading are excluded; returned totals remain reachable. These are function timings, not browser paint or input latency, and carry no CI timing threshold.
| Input | Unbounded milliseconds | Bounded search milliseconds |
|---|---|---|
| Complete replacement | 6492.16, 6777.79, 6786.79 | 5.25, 4.66, 4.19 |
| 100 sparse replacements | 8.19, 4.58, 4.01 | 7.58, 4.26, 4.13 |
| Alternating repeated lines | 3383.13 | 10.26, 7.46, 5.52 |
The bound admits all 100 sparse replacements unchanged. The 129-replacement regression fails without it because the unbounded implementation returns exact counts instead of the required complete-fragment fallback.
Consequences
The browser build includes diff. The bound limits edit-graph search, not wall-clock duration: normalization, fallback rows, and copied output still scale with input length, and expanded cards also derive their rows separately. The content-line rule treats a final newline as a terminator. Regressions cover exact output at 256 edits, complete coarse output above the limit, a sparse edit in 10,000 lines, shared and distant context, repeated lines, copied prefixes, and summary/footer parity. Authored and borrowed Session snapshots cover coarse and exact browser cards respectively.
中文
Problem
文件系统结果元数据携带的前后文本片段包含未改动的上下文。把完整片段分别当作删除和新增会错误标记共享行,并夸大卡片和折叠工具行的统计。
Decision
Web 原语通过维护中的 diff 库生成行补丁,使用 maxEditLength: 256。每处精确改动两侧最多保留三行中性上下文;远距离改动分成独立 hunk,共享上下文不计入增删统计。每个片段的新增与删除行数超过 256 时,搜索停止,完整新旧片段按粗粒度替换呈现,共享行也计入显示、复制和统计。卡片与 diffTotals 使用同一确定性推导。这遵循工具呈现归属决策,仍属于 Client 呈现,不改变持久化元数据或公开 props。
Alternatives considered
无上限比较会让大量改动片段的折叠摘要停顿。确定性的编辑距离上限能让稀疏编辑保持精确,不受文件长度影响;墙钟超时可能使摘要和正文在负载下选择不同结果。粗粒度替换在超过上限后放弃对齐,但保留全部输入行。缓存或异步渲染会增加归属和失效处理,而有界比较不需要这些机制。该呈现行为无需扩展持久化元数据或维护自定义 diff 算法。
Measurement
本地 CPU 诊断用 esbuild(--bundle --platform=node --format=esm)打包生产 DiffBlock.tsx 入口,并在 macOS ARM64 的 Node 26.5.0 下计时 diffTotals。每个片段有一万行:全部替换带唯一索引的行、均匀分布的 100 处替换,或交替重复的 old/shared 与 new/shared 行。不计输入构造和模块加载;返回的统计值保持可达。这些是函数耗时,不是浏览器绘制或输入延迟,也没有作为 CI 时间阈值。
| 输入 | 无上限毫秒数 | 有界搜索毫秒数 |
|---|---|---|
| 全部替换 | 6492.16, 6777.79, 6786.79 | 5.25, 4.66, 4.19 |
| 100 处稀疏替换 | 8.19, 4.58, 4.01 | 7.58, 4.26, 4.13 |
| 交替重复行 | 3383.13 | 10.26, 7.46, 5.52 |
上限允许全部 100 处稀疏替换保持精确。移除上限时,129 处替换回归会失败,因为无上限实现返回精确统计,而非要求的完整片段回退。
Consequences
浏览器构建包含 diff。上限约束编辑图搜索,不约束墙钟时长:规范化、回退行及复制内容仍随输入长度增长,展开卡片也会单独推导正文行。内容行规则把末尾换行视为终止符。回归覆盖 256 次编辑时的精确输出、超过上限后的完整粗粒度输出、一万行中的稀疏编辑、共享和远距离上下文、重复行、复制前缀及摘要与底部统计一致性。编写和借用的 Session 快照分别覆盖粗粒度和精确的浏览器卡片。