reconcile-text 0.4.7 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Reconcile-text: 3-way text merging with automatic conflict resolution
2
+
3
+ A 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 where possible using algorithms inspired by Operational Transformation.
4
+
5
+ **[Try the interactive demo](https://schmelczer.dev/reconcile)** to see it in action.
6
+
7
+ Find it on:
8
+
9
+ - [reconcile-text on crates.io](https://crates.io/crates/reconcile-text)
10
+ - [reconcile-text on NPM](https://www.npmjs.com/package/reconcile-text)
11
+
12
+ ## Key features
13
+
14
+ - **No conflict markers** — Clean, merged output without Git's `<<<<<<<` markers
15
+ - **Cursor tracking** — Automatically repositions cursors and selections during merging
16
+ - **Flexible tokenisation** — Word-level (default), character-level, or custom strategies
17
+ - **Unicode support** — Full UTF-8 support with proper handling of complex scripts
18
+ - **Cross-platform** — Native Rust performance with WebAssembly for JavaScript
19
+
20
+ ## Quick start
21
+
22
+ ### Rust
23
+
24
+ Run `cargo add reconcile-text` or add `reconcile-text` to your `Cargo.toml`:
25
+
26
+ ```toml
27
+ [dependencies]
28
+ reconcile-text = "0.4"
29
+ ```
30
+
31
+ Then merge away:
32
+
33
+ ```rust
34
+ use reconcile_text::{reconcile, BuiltinTokenizer};
35
+
36
+ // Start with original text
37
+ let parent = "Hello world";
38
+ // Two people edit simultaneously
39
+ let left = "Hello beautiful world"; // Added "beautiful"
40
+ let right = "Hi world"; // Changed "Hello" to "Hi"
41
+
42
+ // Reconcile combines both changes intelligently
43
+ let result = reconcile(parent, &left.into(), &right.into(), &*BuiltinTokenizer::Word);
44
+ assert_eq!(result.apply().text(), "Hi beautiful world");
45
+ ```
46
+
47
+ ### JavaScript/TypeScript
48
+
49
+ Install via npm:
50
+
51
+ ```bash
52
+ npm install reconcile-text
53
+ ```
54
+
55
+ Then use in your application:
56
+
57
+ ```javascript
58
+ import { reconcile } from 'reconcile-text';
59
+
60
+ // Same example as above
61
+ const parent = 'Hello world';
62
+ const left = 'Hello beautiful world';
63
+ const right = 'Hi world';
64
+
65
+ const result = reconcile(parent, left, right);
66
+ console.log(result.text); // "Hi beautiful world"
67
+ ```
68
+
69
+ ## Advanced usage
70
+
71
+ ### Edit provenance
72
+
73
+ Track which changes came from where using `reconcileWithHistory`:
74
+
75
+ ```javascript
76
+ const result = reconcileWithHistory(parent, left, right);
77
+ console.log(result.history); // Detailed breakdown of each text span's origin
78
+ ```
79
+
80
+ ### Tokenisation strategies
81
+
82
+ Reconcile offers different ways to split text for merging:
83
+
84
+ - **Word tokeniser** (`BuiltinTokenizer::Word`) — Splits on word boundaries (recommended for prose)
85
+ - **Character tokeniser** (`BuiltinTokenizer::Character`) — Individual characters (fine-grained control)
86
+ - **Line tokeniser** (`BuiltinTokenizer::Line`) — Line-by-line (similar to `git merge` or more precisely [`git merge-file`](https://git-scm.com/docs/git-merge-file))
87
+ - **Custom tokeniser** — Roll your own for specialised use cases
88
+
89
+ ### Cursor tracking
90
+
91
+ Ideal for collaborative editors — Reconcile automatically tracks cursor positions through merges:
92
+
93
+ ```javascript
94
+ const result = reconcile(
95
+ 'Hello world',
96
+ {
97
+ text: 'Hello beautiful world',
98
+ cursors: [{ id: 1, position: 6 }], // After "Hello "
99
+ },
100
+ {
101
+ text: 'Hi world',
102
+ cursors: [{ id: 2, position: 0 }], // At the beginning
103
+ }
104
+ );
105
+
106
+ // Result: "Hi beautiful world" with repositioned cursors
107
+ console.log(result.text); // "Hi beautiful world"
108
+ console.log(result.cursors); // [{ id: 1, position: 3 }, { id: 2, position: 0 }]
109
+ ```
110
+
111
+ ## How it works
112
+
113
+ Reconcile builds upon the foundation of `diff3` but adds intelligent conflict resolution. Given a **parent** document and two modified versions (`left` and `right`), here's what happens:
114
+
115
+ 1. **Diff computation** — Myers' algorithm calculates differences between (parent ↔ left) and (parent ↔ right)
116
+ 2. **Tokenisation** — Text splits into meaningful units (words, characters, etc.) for granular merging
117
+ 3. **Diff optimisation** — Operations are reordered and consolidated to maximise coherent changes
118
+ 4. **Operational Transformation** — Edits are woven together using OT principles, preserving all modifications
119
+
120
+ Whilst Reconcile's primary goal isn't implementing Operational Transformation, OT provides an elegant way to merge Myers' diff output. The same could be achieved with CRDTs, though the quality depends entirely on the underlying 2-way diffs. Note that `move` operations aren't supported, as Myers' algorithm decomposes them into separate `insert` and `delete` operations.
121
+
122
+ ## Background
123
+
124
+ Collaborative editing presents the challenge of merging conflicting changes when multiple users edit documents simultaneously, or when synchronising edits across devices.
125
+
126
+ Traditional solutions like CRDTs or Operational Transformation work well when you control the entire editing environment and can capture every operation. However, many workflows involve users editing with different tools — for example, Obsidian users editing Markdown files with various editors from Vim to Word.
127
+
128
+ This creates **Differential Synchronisation** scenarios [¹]: you 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.
129
+
130
+ The key insight is that whilst 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.
131
+
132
+ > **Note**: Some text domains require more careful handling. Legal contracts, for instance, could have unintended meaning changes from conflicting edits that create double-negations.
133
+
134
+ ## Development
135
+
136
+ ### Prerequisites
137
+
138
+ #### Node.js setup
139
+
140
+ 1. Install [nvm](https://github.com/nvm-sh/nvm):
141
+ ```bash
142
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
143
+ ```
144
+ 2. Install and use Node 22:
145
+ ```bash
146
+ nvm install 22 && nvm use 22
147
+ ```
148
+ 3. Optionally set as default: `nvm alias default 22`
149
+
150
+ #### Rust toolchain
151
+
152
+ 1. Install [rustup](https://rustup.rs):
153
+ ```bash
154
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
155
+ ```
156
+ 2. Install additional tools:
157
+ ```bash
158
+ cargo install wasm-pack cargo-insta cargo-edit
159
+ ```
160
+
161
+ ### Development scripts
162
+
163
+ - **Run tests**: `scripts/test.sh`
164
+ - **Lint and format**: `scripts/lint.sh`
165
+ - **Build demo website**: `scripts/dev-website.sh`
166
+ - **Build demo website**: `scripts/build-website.sh`
167
+ - **Publish new version**: `scripts/bump-version.sh patch`
168
+
169
+ ## License
170
+
171
+ [MIT](./LICENSE)
172
+
173
+ [¹]: https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/35605.pdf