Skip to content

fix(loro.js): record absent-key map deletes and zero counter increments like loro-crdt - #1145

Merged
zxch3n merged 3 commits into
mainfrom
fix/loro-js-local-op-parity
Sep 29, 2026
Merged

zxch3n merged 3 commits into
mainfrom
fix/loro-js-local-op-parity

Conversation

@zxch3n

@zxch3n zxch3n commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Update (merged main at c0ff1255): #1136 already made multi-run text deletes right to left, so this PR now only covers the map-delete and counter cases. The text change, its test, and its changeset bullet were dropped in the merge.

Summary

For two API calls loro.js recorded different ops than loro-crdt. The same edits then got different op IDs, and in one case a different concurrent result:

Call main loro-crdt / this PR
map.delete("absent") no op a delete op. It can win against a concurrent set of that key: peer 1 sets k, peer 2 deletes the absent k, both merge → {} in Rust, { k: 1 } in loro.js on main.
counter.increment(0), attaching a detached counter no op an increment op (Rust's CounterHandler::attach increments by the detached value, even 0)

map.set of the current value stays a no-op in both.

Rationale

loro.js aims at API compatibility with loro-crdt. Op-level parity matters for concurrent outcomes (the map case), for checking out versions inside a multi-run delete, and for any tool that compares histories across the two packages. The differential fuzz harness (#1141) needs it to run peers in lockstep with Rust: without it, most Map/Counter seeds diverge on the first such call.

The old test "does not record semantic no-op edits" asserted the loro.js behavior; it now asserts Rust's op counts. Rust also records movableList.set(i, sameValue); that is left to the MovableList rework (#1132 stack), and the assertion was dropped from this test.

Validation

  • New tests in loro-js/tests/runtime.test.ts for the op counts and for the absent-key delete winning a concurrent set. Both fail on main. The expected values are what loro-crdt (WASM build of this main) produces for the same calls.
  • With this and the other fixes from this session, the harness in lockstep with Rust passes 499 of 500 Map/List/Text/Counter seeds. The remaining seed hits a Rust panic (Op/hint length mismatch, covered by fix: rich-text insert positions near style anchors, delete IDs, and delete events #1135).
  • pnpm --dir loro-js format:check, lint, typecheck, test (415 passed, 1 skipped; with LORO_REQUIRE_WASM_REFERENCE=1 after the merge), build.
  • bench:complexity (1k–8k), 7 alternating rounds vs main, loadavg 30–31: 284 items, none both >1.5× and >0.3 ms slower. Total 1265 ms on main, 1287 ms here. B4 (259,778 actions, 3 samples × 3 alternating rounds, loadavg 23–30): apply medians 305/516/774 ms on main vs 289/500/753 ms here. The load made B4 noisy, but each round was within noise.

🤖 Generated with Claude Code

Three API calls produced different ops than Rust, so the same edits got
different op IDs, and a concurrent map set/delete could resolve
differently:

- Map.delete of an absent key recorded nothing; Rust records a delete.
- Counter.increment(0) and attaching a detached counter with value 0
  recorded nothing; Rust records the increment.
- A text delete across several ID runs was emitted left to right; Rust's
  TextHandler::delete emits the runs right to left.

The old "no semantic no-op edits" test asserted the loro.js behavior; it
now asserts Rust's. MovableList set-to-same-value (Rust records it) is
left to the MovableList rework.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

WASM Size Report

  • Original size: 3277.59 KB
  • Gzipped size: 1089.33 KB
  • Brotli size: 762.83 KB

…parity

Main already writes multi-run text deletes right to left (#1136); keep
its version and drop the duplicate test and changeset bullet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zxch3n zxch3n changed the title fix(loro.js): record the same local ops as loro-crdt fix(loro.js): record absent-key map deletes and zero counter increments like loro-crdt Sep 29, 2026
@zxch3n
zxch3n merged commit 99e7d84 into main Sep 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant