fix: resolve lost updates in multi-user collaboration sync

Concurrent edits to shared notes (e.g., one user checks a shopping list
item while another adds an item) were lost because the sync system used
whole-document Last-Write-Wins. This replaces it with a 3-way line-level
content merge using node-diff3, so changes to different lines are
preserved from both users.

Key changes:
- Add node-diff3 for 3-way line-level content merging
- Remove the timestamp gate in processSyncPush that rejected entire
  offline sync pushes when another user had updated the note
- Add content merge to both the sync push path and the PATCH endpoint,
  using version snapshots as the merge base
- Send baseVersion from the client so the server can find the correct
  common ancestor for 3-way merge
- Protect pending local fields during client pull merge by reading
  the sync queue and preserving queued-but-unpushed changes
- Add comprehensive tests for content merge, server sync, and CRDT merge

https://claude.ai/code/session_01FNpVBBjM6vKzfR8y9NM9Ci
This commit is contained in:
Claude
2026-03-12 20:19:10 +01:00
committed by Fred Cabassut
parent 0965da70b4
commit 57a05bbee0
13 changed files with 520 additions and 72 deletions
+9
View File
@@ -80,6 +80,9 @@ importers:
markdown-it:
specifier: ^14.1.1
version: 14.1.1
node-diff3:
specifier: ^3.2.0
version: 3.2.0
nodemailer:
specifier: ^8.0.2
version: 8.0.2
@@ -3004,6 +3007,10 @@ packages:
resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==}
engines: {node: ^18 || ^20 || >= 21}
node-diff3@3.2.0:
resolution: {integrity: sha512-vLh2xJFSyniBLYDEDbXKqD32fQ5vAxmYT4hco8t0EHQ4CQ4BDHhshi7kdvDc6Y1MwGSi1Mhl4unUukPbCayZdw==}
engines: {bun: '>=1.3.0', node: '>=18'}
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
@@ -6780,6 +6787,8 @@ snapshots:
node-addon-api@8.6.0: {}
node-diff3@3.2.0: {}
node-gyp-build@4.8.4: {}
node-preload@0.2.1: