# Compare and apply tracked changes

> Compare two documents, apply the result as a reviewable tracked change, and check what document shapes are supported before you rely on it.



Document comparison turns two versions of a document into a reviewable tracked change, instead of asking a person to spot the difference by eye. The workflow is `diff.capture` on the target, `diff.compare` against the base, then `diff.apply` with `changeMode: 'tracked'`. The resulting change goes through the same review API as an authored [tracked change](/document-api/tracked-changes).

Not every document shape is eligible for a tracked apply today. Read [applyEligibility before apply](#read-applyeligibility-before-apply) and the [supported-case matrix](#supported-case-matrix) before relying on this workflow for a document you have not already checked.

## Compare two documents [#compare-two-documents]

Capture the target document's snapshot, then compare it against the currently open base document:

```ts
const targetSnapshot = await targetDoc.diff.capture();

const diff = await baseDoc.diff.compare({ targetSnapshot });

console.log(diff.summary.changedComponents);
// e.g. ['body']
```

`diff.summary` reports which components changed (`body`, `comments`, `styles`, `numbering`, `headerFooters`, `parts`) without exposing the underlying comparison mechanics. `diff.payload` is opaque and engine-owned; do not inspect or persist it directly. See the [`diff.compare` reference](/document-api/reference/diff/compare) for the full result shape.

## Read applyEligibility before apply [#read-applyeligibility-before-apply]

`diff.applyEligibility` reports, per change mode, whether the diff is a candidate for apply or already known to be blocked, before you call apply at all:

```ts
const trackedEligibility = diff.applyEligibility?.tracked;

if (trackedEligibility?.status !== 'candidate') {
  throw new Error(`Tracked apply is blocked: ${JSON.stringify(trackedEligibility?.blockers ?? [])}`);
}

try {
  const result = await baseDoc.diff.apply({ diff, changeMode: 'tracked' });
  // Inspect result, then save and review the returned change items.
} catch (error) {
  // Keep the base and report the error's code, message, and details.
  throw error;
}
```

`candidate` means no known comparison-time blocker was found. Apply still checks the live base, structural context and native replay preconditions; it can refuse a candidate. Keep the base open through apply, handle that refusal, and save only a successful result. A candidate does not guarantee that every change in an arbitrary DOCX pair can become a native Word revision.

A blocker names the affected families and a stable code (for example `structural-paragraph-unsupported` or `header-footer-tracked-lifecycle-unsupported`). `diff.apply` enforces the same boundary at apply time: an unsupported family fails the whole call closed, with no partial output and no mutation, rather than silently applying a broader replacement than requested. See the [`diff.apply` reference](/document-api/reference/diff/apply) for every documented family and blocker code.

## Review style changes [#review-style-changes]

For standalone SDK comparisons, request `styleChangeMode: 'tracked'` on both calls:

```ts
const diff = await baseDoc.diff.compare({ targetSnapshot, styleChangeMode: 'tracked' });
const result = await baseDoc.diff.apply({
  diff,
  changeMode: 'tracked',
  styleChangeMode: 'tracked',
});
await baseDoc.save({ out: 'comparison.docx' });
```

This flow preserves the original definitions, creates distinctly named proposed styles,
and tracks the affected style references. Accepting the comparison keeps the proposed
text and styling; rejecting it restores the original text and effective styling. The
saved DOCX carries the revisions for review in Word. Paragraph style references use
formatting revisions; character style changes may use inline replacement revisions.
`result.styleChanges.changeMode` identifies this policy as `tracked`.

Unused definitions can remain after either decision, including an unused style added
by the target. They do not create a visible revision by themselves. The result's
`diagnostics` disclose this retention. Proposed styles have separate names so Word
can retain both versions; this flow does not rename accepted styles back to the target's
original names.

The supported scope is non-default paragraph and character styles used in the main
body, including inherited styles and outline-based TOCs. TOCs that select an affected
style by identity (`\o`) or name (`\t`) are refused. Changes to defaults, table styles, affected styles
used in other stories, simultaneous table topology changes, and inputs with pending
revisions are refused. Adding a definition for an already-used unresolved style is
also refused. Decide pending revisions before making another style comparison.
Numbering and settings keep their existing restrictions. Check eligibility for the
actual pair. The versioned suggest-only review-store surface retains its separate
policy and does not accept this option.

## Definitions and replay limits [#definitions-and-replay-limits]

Style, settings and numbering definitions are shared across the document. Their replacement is not represented by native tracked body revisions. The default tracked policy refuses a changed definition with `definition-replay-tracked-unsupported`, including any accompanying body edit. To adopt changed styles directly while tracking supported body edits, explicitly consent on both calls:

```ts
const diff = await baseDoc.diff.compare({
  targetSnapshot,
  styleChangeMode: 'direct',
});
const result = await baseDoc.diff.apply({
  diff,
  changeMode: 'tracked',
  styleChangeMode: 'direct',
});
```

The result reports direct style adoption separately. Rejecting the tracked body text keeps those adopted styles. This option covers styles; it does not enable tracked settings or numbering replacement. An insertion-only body change creates an insertion revision without requiring a deletion revision.

In standalone sessions, supported table-property replay includes repeating-header flags, cell shading and table borders when the table and cell identities remain stable. Those changes use native property revisions containing the previous properties; they remain subject to other family blockers and live apply preconditions. Native review can accept or reject the property change without changing unchanged cell text or linked content controls. Joined collaboration sessions refuse this property replay before mutation in both modes; use a standalone session for this input. Ordinary paragraph-text edits have a separate collaboration path.

Some precise input boundaries remain:

| Input shape                                                                                    | Tracked compare/apply boundary                                                                                                                                                                                    |
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Multiple new comments in one comparison                                                        | Refused before mutation: `comment-replay-unsafe`, because multiple creations require revision-stable anchor replay. Lossless first-comment carryover is direct and has no independently tracked comment revision. |
| Existing comment topology or an empty retained comment part changes                            | Refused when anchors and target comment parts cannot be replayed losslessly. Keep the comment topology diagnostic and the original pair.                                                                          |
| A text edit inside a paragraph carrying a field, or before ambiguous repeated identical fields | Refused when field identity, placement or run structure cannot be replayed safely. A separate-paragraph edit with retained, unambiguous fields is a different input shape.                                        |
| Clearing pretty-printed paragraph or cell content                                              | A whitespace-only target run fragment is refused with an explicit XML-whitespace diagnostic. Compact-XML and nonempty-edit controls do not establish support for clearing that shape.                             |
| Editing a typed digit in a run with `w:noBreakHyphen`                                          | Refused as unsupported inline replay for that carrier; editing a separate paragraph does not qualify the digit edit.                                                                                              |
| Removing a row that carries a bookmark                                                         | Refused when the removed bookmark has no replayable range. Unbookmarked row deletion does not establish support for whole bookmarked-table deletion.                                                              |
| Editing an earlier table shifts identities in later repeated tables                            | Refused when row or table identities are reassigned. Edits in the last table and single-table controls have different identity boundaries.                                                                        |
| Removing a paragraph immediately before or terminally after a table                            | Some inputs reach the native compiler and refuse `unsupported-context`; other inputs fail earlier table-identity checks. Distant and nonterminal paragraph deletion controls do not qualify these arrangements.   |

A native `base-revision-drift` can mean the live base changed, but it also requires investigating the replay step that reported it. It does not by itself establish a collaboration race. Refresh a genuinely stale comparison; retain an unchanged failing pair and its diagnostic for unsupported replay. The same applies to a candidate refused with `unsupported-context`: report the specific operation and input arrangement instead of treating candidate status as an apply promise.

A document-level proposal or versioned review session has different review semantics from native Word redlines. Use that API only when its preview, accept/reject behavior and preservation of the relevant document structures have been verified for the input. A recommendation in an error message does not qualify a replacement workflow.

## Review and decide [#review-and-decide]

A comparison-generated change is reviewed and decided exactly like an authored one. Follow [List and inspect changes](/document-api/tracked-changes#list-and-inspect-changes) and [Accept or reject one change](/document-api/tracked-changes#accept-or-reject-one-change) — the same `trackChanges.list()`, `trackChanges.get()`, and `trackChanges.decide()` operations apply regardless of whether the change came from a rewrite or a comparison.

> **Comparison-hunk identity (info)**
>
> `trackChanges.list()` exposes each comparison hunk as a parent whose `reviewGroup.kind` and `reviewGroup.groupOrigin`
> are both `comparison-hunk`. Its `reviewGroup.childChangeIds` identify the underlying changes. The review item ids
> returned by `diff.apply().operationReceipts` remain individually addressable through `trackChanges.get()` and
> `trackChanges.decide()`, even though those child ids are not separate list rows.


## Supported-case matrix [#supported-case-matrix]

This historical matrix records the version-qualified support listed below. The precise limits and policy above apply to the current comparison contract; verify the SDK and engine versions you deploy against your saved input pairs.

| Shape                                                | Current behavior                                                                                                                                                                       | Owning issue     |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| Localized, repeated, or separated body-text changes  | Supported in direct and tracked modes. Tracked replacements retain independent comparison-hunk review groups.                                                                          | SD-4953, SD-5189 |
| Body run-formatting changes                          | Supported in direct and tracked modes for verified run-formatting cases; unsupported surrounding structures still fail closed.                                                         | SD-5130          |
| Tables                                               | Supported in verified direct and tracked cases; eligibility identifies known blockers for the exact table shape, while apply validates live replay preconditions.                      | SD-5130          |
| Header/footer in-place content edits                 | Supported in direct and tracked modes.                                                                                                                                                 | SD-3647          |
| Header/footer slot repointing or topology changes    | Supported in direct mode. Tracked mode remains unsupported and fails closed.                                                                                                           | SD-3647          |
| Existing footnote/endnote content                    | Supported in direct and tracked modes.                                                                                                                                                 | SD-5227          |
| New or removed footnote/endnote parts and references | Supported in verified exact-lifecycle cases in both modes. Ambiguous or mixed reference ownership fails closed.                                                                        | SD-5227          |
| Pre-existing pending revisions in the base or target | Supported in both modes when the target revision set can be reconciled exactly; ambiguous replay fails closed.                                                                         | SD-5130          |
| Inline images and drawings                           | Text beside a retained drawing and verified exact image replay are supported. Changed carriers, relationships, media, or opaque sidecars remain eligibility-gated and can fail closed. | SD-5130, SD-5271 |
| Fields and cross-references                          | Verified simple- and complex-field insertion/removal cases are supported. Incomplete, mixed, or changed retained carriers fail closed.                                                 | SD-5130, SD-5216 |
| First comment on a document                          | Lossless plain-text creation is supported in both modes, including initials. Rich or otherwise lossy first-comment creation fails closed.                                              | SD-5130          |
| Content controls (SDT)                               | Unsupported: an SDT in the body currently fails the body family closed, including edits elsewhere in that body.                                                                        | SD-5219          |

This snapshot was verified against `@superdoc/sdk` `2.16.0-next.8` and `@superdoc/docx-engine`
`0.18.0-next.7`. Its source work is tracked by SD-4953 (localized tracked comparison), SD-5130 (reporter
pair eligibility), and SD-3647 (header/footer coverage), with the narrower follow-ups named in the table. Re-run the
Document API comparison suites and update this matrix whenever diff eligibility, replay behavior, or review-group
contracts change. For a runnable version of the workflow above, see the [document comparison
example](https://go.superdoc.dev/examples/document-comparison).

The retained legacy comparison adapter refuses tracked style policy with `CAPABILITY_UNSUPPORTED`; use the current typed engine through the standalone SDK.
