You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
loro.js: an insert concurrent with a delete baked into an imported snapshot lands at the wrong position #1163
When loro.js imports a snapshot, and then an update containing a Text insert that is concurrent with a delete already applied in that snapshot, the insert lands at the wrong position. The latest state is wrong, and exports persist the error: Rust re-imports the wrong text. Importing the same history as updates only gives the right result.
This was found in the third review of the loro.js snapshot-checkout PRs (#1126 → #1127 → #1128). It reproduces on main (29553d5b), so those PRs did not introduce it.
Reproduction
The writer is loro-crdt WASM built from 29553d5b:
consta=newLoroDoc();a.setPeerId(1);a.getText("t").insert(0,"abcdef");a.commit();constb=newLoroDoc();b.setPeerId(2);b.import(a.export({mode: "snapshot"}));b.getText("t").insert(4,"X");b.commit();// between "d" and "e"constupdate=b.export({mode: "update",from: a.oplogVersion()});a.getText("t").delete(2,2);a.commit();// deletes "cd"constsnapshot=a.export({mode: "snapshot"});
reader
text
Rust: import(snapshot), then import(update)
abXef
loro.js main: import(snapshot), then import(update)
abefX
loro.js main: importBatch([snapshot, update])
abefX
loro.js main: import every change as updates, then import(update)
abXef
Rust re-importing loro.js's snapshot export
abefX
The review also reports that on styled Rust text that loro.js cannot replay, the insert lands after the text: abYefX, where Rust gives abYXef.
Likely cause
A Text state hydrated from a snapshot has no tombstones for the characters deleted before the snapshot. loro.js therefore maps the concurrent insert onto the snapshot's visible text instead of placing it next to its deleted neighbours. On #1126, after a checkout that crosses one of that container's snapshot operations, the container is replayed from its history, and the same import gives abXef.
Possible fix
Before applying an imported operation that is concurrent with the snapshot's operations on a snapshot-hydrated sequence, replay that container's history first, as #completeSnapshotSequence does on #1126. Rust rich text that loro.js cannot replay would also need the anchor model from #1137.
Activity
fix(loro.js): correct checkout after a snapshot import (#1126)
fix(loro.js): keep root-time map values and tree nodes when a shallow…
fix(loro.js): drop containers deleted before the shallow root, keep t…
fix(loro.js): model MovableList like Rust (positions and LWW elements) (
fix(loro.js): make import and importBatch atomic (#1167)
fix(loro.js): reject invalid MovableList move/set refs (port Rust #1125…