@ai-matrx/diff

中文文档
0.6.35发布于 2026/10/28MITTypeScriptESM + CJS近 30 天下载 5.6万

The AI Matrx diff product: a line-level LCS text engine, a structural tree/array engine, and the complete React viewer — split/inline/highlight views, word-level highlights, folding, per-hunk accept/reject merging, and the structured entity diff with its

原始使用文档
文档目录9 节
  1. React viewer
  2. Structured (entity) diff
  3. Styling — the token contract
  4. The one injection seam: a heavy (code editor) renderer
  5. Text engine
  6. Structural engine
  7. Design
  8. License
  9. Review sessions

@ai-matrx/diff

The complete AI Matrx diff product: two pure engines plus the React viewer built on them.

  • @ai-matrx/diff/react — the whole UI. DiffViewer (the core everything else composes), TextDiff, DiffReview (per-hunk merge), InlineTextDiff, AnimatedDiffReveal, and the structured entity viewer DiffViewerShell with its field adapters. No icon library, no UI kit, no editor dependency.

  • The engines below are pure logic, zero dependencies, no DOM, nothing at import time:

  • @ai-matrx/diff/text — line-level LCS text diff. One computation emits both the unified (inline) and side-by-side (rows) representations, with word/char-level highlight segments on changed line pairs and a hunk model that turns the diff into an accept/reject merge tool.

  • @ai-matrx/diff/structural — generic tree/array structural diff. Deep-diffs two plain objects into a tree of typed change nodes with pluggable per-field identity keys for array matching.

The root (@ai-matrx/diff) re-exports both engines. /react is a separate subpath so a headless consumer never pays for the UI.

npm install @ai-matrx/diff

React viewer

import { DiffViewer, DiffReview } from "@ai-matrx/diff/react";

// Read-only. Fills its container: render it as a route, a window, a modal, a
// sheet, or any region of a page.
<DiffViewer
  original={a}
  modified={b}
  engine="auto"          // "auto" | "light" | "monaco"
  language="typescript"  // drives auto selection; omit for prose/markdown
  originalLabel="Before"
  modifiedLabel="After"
  defaultView="split"    // "split" | "inline" | "highlight"
/>

// Merge. Every hunk is pending / applied / rejected; Apply hands back the text.
<DiffReview original={a} modified={b} onApply={(merged) => save(merged)} />

Views: split (side-by-side), inline (unified), highlight (single-pane — the new document as flowing prose with only the changed regions tinted; the reader's view, not a code diff). Unchanged context folds to a few lines around each change and expands per run; Prev/Next jump between changes; Swap flips which side is the baseline. The diff is computed ONCE, so resolving, expanding and navigating never re-run the LCS.

Structured (entity) diff

import { computeDiff } from "@ai-matrx/diff/structural";
import {
  DiffViewerShell,
  createAdapterRegistry,
  TextFieldAdapter,
  TagsFieldAdapter,
} from "@ai-matrx/diff/react";

const adapters = createAdapterRegistry();
adapters.register("instructions", TextFieldAdapter);
adapters.register("tags", TagsFieldAdapter);

<DiffViewerShell
  diffResult={computeDiff(oldRecord, newRecord)}
  oldValue={oldRecord}
  newValue={newRecord}
  oldLabel="v3"
  newLabel="v4"
  adapters={adapters}
  temporalMetadata={dates}  // optional per-side / per-field provenance
/>

Fields with no registered adapter fall back to the package's DefaultFieldAdapter — never dropped. Provenance rows are explicit about what they do not know (loading, unavailable) and never substitute a record-wide timestamp for a missing field date.

Styling — the token contract

Components consume color ONLY through the semantic token vocabulary (--card, --muted-foreground, --border, --primary, --success, --destructive, …). Two host steps:

/* Tailwind v4: compile the package's utilities. */
@source "../node_modules/@ai-matrx/diff/dist";
// Fresh app with no token vocabulary of its own — gives every token a
// sensible light/dark value. Apps that already define the AI Matrx tokens
// must NOT import this.
import "@ai-matrx/diff/styles.css";

Red-means-removed / green-means-added is deliberate and non-configurable (it is the universal diff convention, not a brand color); everything else — surfaces, borders, text, fills — follows the host's tokens.

The one injection seam: a heavy (code editor) renderer

The package carries no code-editor dependency. Register one and DiffViewer routes source code and very large inputs to it:

import { setHeavyDiffRenderer, MONACO_DIFF_EDITOR_OPTIONS } from "@ai-matrx/diff/react";

setHeavyDiffRenderer(MyMonacoDiff); // once, at module scope

With none registered the light engine renders a complete diff — that is the working default, not a stand-in. Only an explicit engine="monaco" that cannot be honored is degraded, and it says so in the toolbar and through onEngineResolved.

Text engine

import { computeTextDiff, applyHunks } from "@ai-matrx/diff/text";

const diff = computeTextDiff("the quick fox\nsecond line", "the slow fox\nsecond line");

diff.stats;          // { additions: 0, deletions: 0, modifications: 1, unchanged: 1 }
diff.inline;         // unified view: one entry per line per side (removed, then added)
diff.rows;           // split view: one aligned row per pair, left/right cells
diff.rows[0].left.segments;
// word-level highlights on the modified pair:
// [{ type: "unchanged", value: "the " }, { type: "removed", value: "quick" },
//  { type: "unchanged", value: " fox" }]
diff.whitespaceOnly; // true when only whitespace differs

// Merge tool: accept/reject hunks, get the merged text back.
// Accept-all === modified, accept-none === original — exactly.
applyHunks(original, modified, [0, 2]); // accept hunks 0 and 2, keep the rest old

Behavior notes: removed/added lines in the same change block pair up as modifications (with intra-line segments when the pair is at least 25% similar); inputs too large for the dense LCS matrix degrade safely to a whole-block replace instead of OOMing; ignoreTrailingWhitespace is a display option — the hunk/merge model always diffs raw lines so merges round-trip byte-exactly. maxLcsCells tightens the matrix cap.

Two further line readings share the same bounded alignment:

import { computeLineChanges, analyzeTextChange } from "@ai-matrx/diff/text";

// A code editor's "+N −M": an edited line is one deletion plus one addition.
const { additions, deletions, lines } = computeLineChanges(before, after);

// A sync conflict's content-loss reading (who holds lines the other lacks,
// lines/chars changed, whitespace/blank-line/trim-only flags, merged segments).
const a = analyzeTextChange(local, remote, { maxLcsCells: 250_000 });
a.modifiedHasContentOriginalLacks; // keeping `local` would lose remote's lines

Above the cap both keep the common prefix and suffix and read the changed middle as one removed block then one added block, and say degraded: true.

Structural engine

import { computeDiff, filterChangesDeep } from "@ai-matrx/diff/structural";

const result = computeDiff(
  { title: "Old", items: [{ id: "a", qty: 1 }, { id: "b", qty: 2 }] },
  { title: "New", items: [{ id: "b", qty: 2 }, { id: "a", qty: 5 }] },
  { identityKeys: { items: "id" } }, // or (item, index) => string
);

result.hasChanges; // true
result.stats;      // { added, removed, modified, unchanged, total }
result.root;       // DiffNode tree: title modified; items modified with children:
                   //   "a" modified (qty 1 → 5), "b" reordered
filterChangesDeep(result.root); // prune everything unchanged

Behavior notes: object key order is ignored by default (opt in per path via orderSensitiveObjectPaths); underscore-prefixed keys are compared by default — schema contract keys like __kind are data (skipUnderscorePrefix: true is an explicit opt-in presentation filter); heterogeneous arrays ([{...}, "str"], nested arrays, nulls) never throw — a mismatched pair is a modified leaf; when a configured identity key is not unique, the engine falls back to lossless positional matching rather than silently dropping items; excludePaths takes bare names for root fields or dotted paths for nested ones.

diffRecordFields(prior, next, rowChange) is the top-level reading of ONE row write, for a row-history timeline: inserted / deleted / changed fields, a JSON column as one whole change (equality is === or matching JSON.stringify, so key order counts), and an "insert" or "delete" row reports every field as inserted or deleted.

Design

  • Pure engines, dual ESM/CJS with matching type declarations, verified with a packed-tarball install loading every entry point through both import and require. The /react chunks carry the "use client" boundary; the engine entries deliberately do not, so they stay legal inside a React Server Component. The tarball gate checks both directions.
  • Both engines run happily during render; every quadratic path is capped and degrades instead of throwing or hanging.
  • Strict types throughout: exactOptionalPropertyTypes, noUncheckedIndexedAccess, zero any.
  • react / react-dom are OPTIONAL peers — only /react needs them.

License

MIT

Review sessions

DiffReview requires every hunk to be accepted or rejected before Apply. For a host-owned session, create it from @ai-matrx/diff/text, pass session and an onTransition reducer boundary; otherwise pass original and modified for the source-keyed internal session. Set disabled while the host is pending.

版本与发布记录

@ai-matrx/diff 共发布 53 个版本,首次发布于 2026/08/29;过去 12 个月发布了 53 个版本。

版本发布日期距今
0.6.35latest2026/10/288 天前
0.6.342026/10/289 天前
0.6.332026/10/2610 天前
0.6.322026/10/2610 天前
0.6.312026/10/2610 天前
0.6.302026/10/2412 天前

关于 @ai-matrx/diff 的常见问题

@ai-matrx/diff 是什么?

The AI Matrx diff product: a line-level LCS text engine, a structural tree/array engine, and the complete React viewer — split/inline/highlight views, word-level highlights, folding, per-hunk accept/reject merging, and the structured entity diff with its

如何安装 @ai-matrx/diff?

在项目目录运行 npm install @ai-matrx/diff,或使用 pnpm add / yarn add / bun add。

@ai-matrx/diff 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ai-matrx/diff 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ai-matrx/diff 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@ai-matrx/diff 可以商用吗?

@ai-matrx/diff 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ai-matrx/diff 的下载量有多少?

根据 npm 官方下载统计,@ai-matrx/diff 在 2026/10/05 – 2026/11/03 这 30 天内被下载 55,928 次,日均约 1,864 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ai-matrx/diff 还在维护吗?

最新版本 0.6.35 发布于 2026/10/28(8 天前);至今共发布 53 个版本,其中过去 12 个月发布了 53 个;首次发布于 2026/08/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。