> **本文回答**：文档变更对比 = `DiffUtils.diff(oldJson, newState)` 返回 `IDiffResult`，包含 `summary.{addedEntities, deletedEntities, modifiedEntities}` 和 `allItems[].{changeType, label, description}`。可视化用 `getDiffPanel().setDiffResult(result); panel.show()`。

## 一、最小用法

```js
import { DiffUtils, getDiffPanel } from 'vjcad';

// 1. 拍快照
const oldState = Engine.currentDoc.toDb();
const oldJson  = JSON.stringify(oldState);

// 2. 执行修改 ...

// 3. 计算差异
const newState = Engine.currentDoc.toDb();
const result   = await DiffUtils.diff(oldJson, newState);

if (result.hasChanges) {
  const s = result.summary;
  console.log(`+${s.addedEntities} 新增, -${s.deletedEntities} 删除, ~${s.modifiedEntities} 修改`);

  result.allItems.forEach(item => {
    console.log(`[${item.changeType}] ${item.label} - ${item.description}`);
  });

  // 可视化
  const panel = getDiffPanel();
  panel.setDiffResult(result);
  panel.show();
}
```

MCP 参考：`vjcad/example/file/10diff.js`。

## 二、`IDiffResult` 结构

| 字段 | 说明 |
|---|---|
| `hasChanges` | 是否有变更 |
| `summary.addedEntities` | 新增实体数 |
| `summary.deletedEntities` | 删除实体数 |
| `summary.modifiedEntities` | 修改实体数 |
| `allItems` | 所有变更项数组 |
| `allItems[].changeType` | 变更类型：`'added' \| 'deleted' \| 'modified'` |
| `allItems[].label` | 项目标签 |
| `allItems[].description` | 描述（modified 项为变更属性路径列表） |
| `summary.addedLayers / deletedLayers / modifiedLayers` | 图层增 / 删 / 改数 |
| `summary.modifiedTextStyles / modifiedDimStyles / modifiedDocEnv` | 文字样式 / 标注样式 / 文档环境变量改动数 |
| `allItems[].category` | `'entity' \| 'layer' \| 'textStyle' \| 'dimStyle' \| 'docEnv'` |
| `allItems[].key` / `entityId` / `blockName` | 唯一键 / 实体 id（可用于定位）/ 所在块名 |
| `allItems[].changes` | modified 项的属性变更 `[{path, oldValue, newValue}]` |

> **"修改"的判定**：实体在新旧快照里都存在时，只有**新快照里带 `_modified` 标记**（`entity.setModified()` 会置位，`toDb()` 输出 `_modified: true`）的实体才会深比较属性，有差异才计入 `modifiedEntities`。直接改字段不调 `setModified()` 的实体不会被识别为修改；`SAVESERVER` 成功后会清掉这些标记。

MCP 参考：`vjcad/example/file/10diff.js`（类型 `IDiffSummary` / `IDiffListItem`）。

### 与原始快照对比的快捷方法

```js
// 对比当前文档与打开时的基准（serverSource.originalJsonCompressed）
const r = await DiffUtils.diffCurrentDoc();   // 没有基准（本地新图 / 未 setOriginalJson）时返回 null
if (r?.hasChanges) getDiffPanel().setDiffResult(r);
```

## 三、典型场景

```js
// 模式 1：审计模式 — 一次性对比快照
const baseline = JSON.stringify(Engine.currentDoc.toDb());
// ... 用户操作 ...
const result = await DiffUtils.diff(baseline, Engine.currentDoc.toDb());

// 模式 2：保存前预览
const original = await Engine.currentDoc.getOriginalJson();
const current  = JSON.stringify(Engine.currentDoc.toDb());
const r        = await DiffUtils.diff(original, JSON.parse(current));
if (r.summary.deletedEntities > 100) {
  alert('删除超过 100 个实体，请确认！');
}
```

MCP 参考：`vjcad/example/file/10diff.js`。

## 四、与服务端增量保存的关系

`SAVESERVER` 的增量保存内部就是用 `originalJson` ↔ `currentJson` 算 diff（详见 [server](server.md)）。`DiffUtils.diff` 让你能**在本地预览同样的变更集**，做以下事情：

- 显示 "本次保存的变更" 给用户确认
- 实现"撤销到某一历史"的可视化对比
- 多分支合并前预览冲突点

## 反例

- 拿 `JSON.stringify(toDb()) === JSON.stringify(toDb())` 比较：实体顺序、引用、序列化精度都可能造成误判，**用 `DiffUtils.diff` 而非字符串比较**。
- 频繁 diff 大图纸（每秒一次）：性能问题，建议节流（throttle）到 1~5s。
- 语义：两个快照之间"先 addEntities 再 eraseEntities"的实体**净零**——既不进 `addedEntities` 也不进 `deletedEntities`（diff 按最终状态对比，不是操作日志）。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/file/file-operations.md`、`vjcad/doc/file/index.md`
- 示例：`vjcad/example/file/10diff.js`
