Repository navigation
fix: rich-text insert positions near style anchors, delete IDs, and delete events - #1135
Merged
Merged
Conversation
- `get_entity_index_for_text_insert` reused a cached cursor at a text-leaf boundary, skipping the style-anchor rules of `find_best_insert_pos`. - `get_text_entity_ranges` advanced a range's start ID by its UTF-16 length. - `change_to_diff` could not give one op to several event hints. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 27, 2026
Contributor
WASM Size Report
|
Review follow-up; rationale in context/richtext-insert-positions.md. - Reuse the cached cursor wherever the anchor rules cannot move the insert, which brings typing in styled text back to main's speed. - Test cached against cold insert lookups on random styled documents. - Check that change_to_diff pairs ops with hints of the same kind and consumes every hint. - The cache bug also affects native builds, the panic needs only an UndoManager, and old delete IDs stay wrong (#1149). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member
Author
|
Review follow-up, in 4749483 (the first commit is unchanged):
Also added Validation: Typing benchmark, median ms of 15 interleaved rounds (100k-char text, 100k inserts):
|
zxch3n
added a commit
that referenced
this pull request
Sep 28, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three rich-text bugs found by the randomized loro.js ↔ Rust differential suite (#1136). Each has a regression test.
get_entity_index_for_text_insertreused a cached cursor at a text-chunk boundary, sofind_best_insert_posdid not run and the side of a style anchor that new text landed on depended on earlier lookups. E.g.insert "b",mark(0..1, em /*expand: before*/),unmark(0..1, bold)(writes nothing),insert(1, "a")gavebaall inem; without the no-op unmark,ais outsideem.anchor_rules_may_move_insert): inside or between text chunks, at the text ends, or at the end of a text chunk followed by an anchor that new text stays in front of (typing at the end of bold). Elsewhere, fall back tofind_best_insert_pos.start_idoff by UTF-16 lengthget_text_entity_rangesmerged two chunks whenstart_id + event_lenequaled the next ID. In WASMevent_lenis UTF-16, so after an astral character it could merge the wrong chunk: deletinga😀xina😀xb(x typed between 😀 and b) recordedstart_id0 with length 3, naminga😀b. A full import applies deletes by position and gets the right text, but a shallow-snapshot import labels its placeholders with the recorded IDs and a cursor on the deletedxstops resolving, so both disagree with the full import (b!vsx!in #1149).loro-crdt≤ 1.16.3 keep the wrong IDs: #1149.Op/hint length mismatchpanicUndoManager), backspacing over an astral character merges the ops (Unicode lengths) but not the event hints (UTF-16 spans).change_to_diffcould only split an op when a hint spanned several ops. E.g.insert "𝒳y",delete(2, 1),delete(0, 2),commitpanicked.change_to_diffnow also debug-checks that each op matches its hint's kind and that every hint is consumed.Rationale for the cache rule:
context/richtext-insert-positions.md.Performance
cargo run -p loro --release --example richtext_typing_bench -- <scenario> <commit|txn>: 100k-character text, then 100k single-character inserts at consecutive positions, committing after each insert (commit) or once (txn). Median ms of 15 interleaved rounds (fresh process per run, loaded machine):mainmainmainplainplainin-boldin-boldbold-endbold-endmarks-2000marks-2000"First version" is this PR's first commit, which only reused the cache strictly inside a text chunk.
bold-endtypes at the end of a bold range: the text goes before bold's end anchor, which the cache now handles because the anchor rules stop at that anchor.Validation
insert_position_near_style_anchors_does_not_depend_on_cursor_cache(crates/loro/tests/contracts/text_richtext_advanced.rs) fails without the fix.cached_insert_position_matches_cold_lookup_near_style_anchors(crates/loro-internal/src/container/richtext/richtext_state/insert_pos_cache_test.rs): random documents with all four expand types, overlapping styles, null/false values, unmarks, empty and adjacent anchor pairs, and astral/ZWJ/flag/modifier text; after every edit it compares the cached lookup with a cold one at every position for all fivePosTypes, from the edit's cursor and from the previous position's. 12 seeds (~4 s debug) in CI; 1,000 seeds pass in release both natively and with--features wasm(LORO_INSERT_CACHE_SEEDS=1000). It fails onmain's cache rule and on three looser variants of the new one.crates/loro-wasm/tests/richtext.test.tsfor the delete IDs, the subscriber panic, and theUndoManager-only panic (UTF-16 only, so not reproducible natively).cargo test -p loro -p loro-internal: 1,181 passed, 0 failed (15 ignored).pnpm -C crates/loro-wasm build-dev, debug assertions on, so the newchange_to_diffchecks run on UTF-16 hints): vitest 381 passed in 29 files;tsc, deno, and bun tests pass.pnpm check(clippy, all features,-D warnings) passes.Related
🤖 Generated with Claude Code