A Rust and TypeScript library for merging conflicting text edits without manual intervention. Unlike traditional 3-way merge tools that produce conflict markers, reconcile-text automatically resolves conflicts by applying both sets of changes (while updating cursor positions) using an algorithm inspired by Operational Transformation.
✨ Try the interactive demo to see it in action!
cargo add reconcile-text(reconcile-text on crates.io)npm install reconcile-text(reconcile-text on NPM)
- No conflict markers — Clean, merged output without Git's
<<<<<<<markers - Cursor tracking — Automatically repositions cursors and selections throughout the merging process
- Flexible tokenisation — Word-level (default), character-level, line-level, or custom tokenisation strategies
- Unicode support — Full UTF-8 support with proper handling of complex scripts and grapheme clusters
- Cross-platform — Native Rust performance with WebAssembly bindings for JavaScript environments
Install via crates.io:
cargo add reconcile-textAlternatively, add reconcile-text to your Cargo.toml:
[dependencies]
reconcile-text = "0.5"Then start merging:
use reconcile_text::{reconcile, BuiltinTokenizer};
// Start with the original text
let parent = "Hello world";
// Two users edit simultaneously
let left = "Hello beautiful world"; // Added "beautiful"
let right = "Hi world"; // Changed "Hello" to "Hi"
// Reconcile combines both changes
let result = reconcile(parent, &left.into(), &right.into(), &*BuiltinTokenizer::Word);
assert_eq!(result.apply().text(), "Hi beautiful world");See the merge-file example for another example or the library's documentation.
Install via NPM:
npm install reconcile-textThen use it in your application:
import { reconcile } from 'reconcile-text';
// Start with the original text
const parent = 'Hello world';
// Two users edit simultaneously
const left = 'Hello beautiful world';
const right = 'Hi world';
const result = reconcile(parent, left, right);
console.log(result.text); // "Hi beautiful world"See the example website source for a more complex example or the advanced examples document.
Collaborative editing presents the challenge of merging conflicting changes when multiple users edit documents simultaneously or asynchronously whilst offline. Traditional solutions like Conflict-free Replicated Data Types (CRDTs) or Operational Transformation (OT) work well when you control the complete editing infrastructure and can capture every individual operation (1). However, many workflows involve users editing with various tools, for example, Obsidian users editing Markdown files with various editors ranging from Vim to VS Code.
This creates Differential Synchronisation scenarios (2, 3): we only know the final state of each document, not the sequence of operations that produced it. This is the same challenge Git addresses, but Git requires manual conflict resolution. The key insight is that while incorrect merges in source code can introduce bugs, human text is more forgiving: a slightly imperfect sentence is often preferable to conflict markers interrupting the flow.
Note: Some text domains require more careful handling. Legal contracts, for instance, could have unintended meaning changes from conflicting edits that create double negations. At the same time, semantic conflicts can still arise when merging code, even in the absence of syntactic conflicts.
Differential sync is implemented by universal-sync and my Obsidian plugin vault-link, and it requires a merging tool which creates conflict-free results for the best user experience.
reconcile-text starts off similarly to diff3 (4, 5) but adds automated conflict resolution. Given a parent document and two modified versions (left and right), the following happens:
- Tokenisation — Input texts get split into meaningful units (words, characters, etc.) for granular merging
- Diff computation — Myers' algorithm calculates differences between (parent ↔ left) and (parent ↔ right)
- Diff optimisation — Operations are reordered and consolidated to maximise chained changes
- Operational Transformation — Edits are woven together using OT principles, preserving all modifications and updating cursors
Whilst the primary goal of reconcile-text isn't to implement OT, it provides an elegant way to merge Myers' diff outputs. (For a dedicated Rust OT implementation, see operational-transform-rs.) The same could be achieved with CRDTs, which many libraries implement well for text—see Loro, cola, and automerge as excellent examples.
However, when only the end result of concurrent changes is observable, merge quality depends entirely on the quality of the underlying 2-way diffs. For instance, move operations cannot be supported because Myers' algorithm decomposes them into separate insert and delete operations, regardless of the merging algorithm used.
Contributions are welcome!
- Install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash - Install and use Node 22:
nvm install 22 && nvm use 22 - Optionally, set as default:
nvm alias default 22
Install rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh- Run tests:
scripts/test.sh - Lint and format:
scripts/lint.sh - Develop demo website:
scripts/dev-website.sh - Build demo website:
scripts/build-website.sh - Publish new version:
scripts/bump-version.sh patch