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.

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

Compare two documents

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

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 for the full result shape.

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:

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 for every documented family and blocker code.

Review style changes

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

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

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:

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 shapeTracked compare/apply boundary
Multiple new comments in one comparisonRefused 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 changesRefused 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 fieldsRefused 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 contentA 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:noBreakHyphenRefused as unsupported inline replay for that carrier; editing a separate paragraph does not qualify the digit edit.
Removing a row that carries a bookmarkRefused 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 tablesRefused 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 tableSome 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

A comparison-generated change is reviewed and decided exactly like an authored one. Follow List and inspect changes and 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.

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.

ShapeCurrent behaviorOwning issue
Localized, repeated, or separated body-text changesSupported in direct and tracked modes. Tracked replacements retain independent comparison-hunk review groups.SD-4953, SD-5189
Body run-formatting changesSupported in direct and tracked modes for verified run-formatting cases; unsupported surrounding structures still fail closed.SD-5130
TablesSupported 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 editsSupported in direct and tracked modes.SD-3647
Header/footer slot repointing or topology changesSupported in direct mode. Tracked mode remains unsupported and fails closed.SD-3647
Existing footnote/endnote contentSupported in direct and tracked modes.SD-5227
New or removed footnote/endnote parts and referencesSupported 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 targetSupported in both modes when the target revision set can be reconciled exactly; ambiguous replay fails closed.SD-5130
Inline images and drawingsText 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-referencesVerified 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 documentLossless 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.

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

On this page