Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
970e9b7
perf: improve text checkout scalability
zxch3n Apr 22, 2026
e4a9181
docs: add text checkout performance plan
zxch3n Apr 22, 2026
ed54924
docs: remove temporary text checkout plan
zxch3n Apr 22, 2026
377afd4
perf: batch rich text style event deltas
zxch3n Apr 22, 2026
7b20b7e
Merge remote-tracking branch 'origin/main' into feat/scale-text-check…
zxch3n Apr 22, 2026
7d27feb
fix: handle fuzzed text checkout edge cases
zxch3n Apr 22, 2026
4ce926f
fix: handle shallow root frontiers in fuzzed imports
zxch3n Apr 22, 2026
c259b80
Merge remote-tracking branch 'origin/main' into feat/scale-text-check…
zxch3n Apr 26, 2026
67905cb
Merge remote-tracking branch 'origin/main' into feat/scale-text-check…
zxch3n May 7, 2026
87dd333
fix: clear deleted cache on checkout
zxch3n May 8, 2026
6e1b49a
fix: reject partial shallow root checkout
zxch3n May 8, 2026
162ff39
fix: reexport shallow root snapshots
zxch3n May 8, 2026
dccd172
fix: avoid shallow gca on reexport
zxch3n May 8, 2026
4ce3408
fix: reject unreachable shallow frontiers
zxch3n May 8, 2026
df2498c
fix: reject shallow root dependency frontiers
zxch3n May 8, 2026
fdedbd7
fix: guard shallow frontier utilities
zxch3n May 8, 2026
5d046be
fix: clamp shallow frontier conversions
zxch3n May 8, 2026
05a5b62
fix: clamp empty shallow version frontiers
zxch3n May 8, 2026
0b656b7
fix: normalize shallow reexport frontiers
zxch3n May 8, 2026
3e9edca
fix: normalize shallow snapshot targets
zxch3n May 8, 2026
20770e3
fix: normalize state-only export frontiers
zxch3n May 8, 2026
b7763b0
fix: normalize snapshot-at frontiers
zxch3n May 8, 2026
530b901
fix: keep richtext style pairs in shallow roots
zxch3n May 8, 2026
adbe9a3
fix: clamp shallow diff lca frontiers
zxch3n May 8, 2026
394134e
fix: normalize shallow state-only targets
zxch3n May 8, 2026
226cf27
fix: preserve independent shallow root frontiers
zxch3n May 8, 2026
9b45cd3
fix: handle multi-frontier shallow snapshot checkout
zxch3n May 8, 2026
ca54408
fix: reject malformed imported text diffs
zxch3n May 9, 2026
a38f5d5
fix: reject empty text marks in JSON import
zxch3n May 9, 2026
37078ae
fix: reject unpaired text marks in JSON import
zxch3n May 9, 2026
b0257ee
fix: canonicalize frontiers constructors
zxch3n May 9, 2026
3622d49
fix: preserve canonical state-only snapshot frontiers
zxch3n May 9, 2026
d3d84bb
fix: ignore cyclic tree moves in one-doc fuzz
zxch3n May 9, 2026
466c97e
fix: preserve commit options after failed change travel
zxch3n May 10, 2026
1bf267d
Merge branch 'main' into feat/scale-text-checkout-perf
zxch3n May 21, 2026
4a32d2e
fix: tighten import rollback followups
zxch3n May 21, 2026
2b9a599
Merge branch 'feat/scale-text-checkout-perf' of https://github.com/lo…
zxch3n May 21, 2026
d67d534
refactor: centralize import rollback container check
zxch3n May 21, 2026
507aff6
docs: plan fast diff calc span routing
zxch3n May 22, 2026
c350b0e
bench: add many text checkout scenario
zxch3n May 22, 2026
5c3cd62
refactor: route richtext checkout through spans
zxch3n May 22, 2026
91e5ceb
perf: filter richtext checkout spans by coverage
zxch3n May 22, 2026
b8ee18f
docs: record fast diff calc benchmark results
zxch3n May 22, 2026
57bfd67
bench: report checkout span averages
zxch3n May 22, 2026
f9fb539
test: compare filtered richtext diff
zxch3n May 22, 2026
434b35c
docs: update fast diff calc commit list
zxch3n May 22, 2026
f8d5752
fix: keep list diff calculator small
zxch3n May 22, 2026
82dd1dc
perf: reuse coverage-local richtext tracker versions
zxch3n May 22, 2026
091e3c9
fix: guard richtext tracker reuse
zxch3n May 22, 2026
3c2b53e
test: skip shallow peers in gc fuzzer sync
zxch3n May 22, 2026
3d31441
Merge remote-tracking branch 'origin/main' into feat/scale-text-check…
zxch3n May 26, 2026
079709f
fix: harden checkout replay invariants
zxch3n May 26, 2026
95b6353
Merge remote-tracking branch 'origin/main' into feat/scale-text-check…
zxch3n Jun 2, 2026
6305ea8
Merge remote-tracking branch 'origin/main' into feat/scale-text-check…
zxch3n Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: normalize shallow reexport frontiers
  • Loading branch information
zxch3n committed May 8, 2026
commit 0b656b71ff3898563585074a53c58d39d9478255
7 changes: 4 additions & 3 deletions crates/loro-internal/src/encoding/shallow_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
dag::DagUtils,
encoding::fast_snapshot::{_encode_snapshot, Snapshot},
state::container_store::FRONTIERS_KEY,
version::{Frontiers, VersionVector},
version::{shrink_frontiers, Frontiers, VersionVector},
LoroDoc,
};

Expand Down Expand Up @@ -227,8 +227,9 @@ fn calc_shallow_doc_start(oplog: &crate::OpLog, frontiers: &Frontiers) -> Fronti
if !oplog.shallow_since_vv().is_empty() {
// The target frontiers have already been checked by the caller. On a
// shallow doc, searching for a lower GCA can walk into trimmed history.
// Keep the requested boundary instead.
return frontiers.clone();
// Keep the requested boundary, but normalize redundant frontiers so the
// exported shallow root does not include an op and its ancestor together.
return shrink_frontiers(frontiers, oplog.dag()).unwrap_or_else(|_| frontiers.clone());
}

// Find the LCA of the given frontiers by iteratively pairwise GCA.
Expand Down
53 changes: 53 additions & 0 deletions crates/loro/tests/integration_test/shallow_snapshot_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,25 @@ fn state_only_export_preserves_multi_frontier_shallow_root() -> anyhow::Result<(
Ok(())
}

#[test]
fn state_only_multi_frontier_shallow_root_can_accept_local_edits() -> anyhow::Result<()> {
let (bytes, shallow_root, _) = multi_frontier_shallow_snapshot()?;
let imported = LoroDoc::new();
imported.import(&bytes)?;
let state_only = imported.export(ExportMode::state_only(Some(&shallow_root)))?;

let edited = LoroDoc::new();
edited.import(&state_only)?;
edited.set_peer_id(3)?;
edited.get_text("tail").insert(0, "tail")?;
edited.commit();

assert!(edited.is_shallow());
assert_eq!(edited.shallow_since_frontiers(), shallow_root);
assert_eq!(edited.get_text("tail").to_string(), "tail");
Ok(())
}

#[test]
fn state_correctness_check_handles_multi_frontier_shallow_root() -> anyhow::Result<()> {
let (bytes, _, _) = multi_frontier_shallow_snapshot()?;
Expand All @@ -273,6 +292,38 @@ fn state_correctness_check_handles_multi_frontier_shallow_root() -> anyhow::Resu
Ok(())
}

#[test]
fn reexport_shallow_snapshot_with_redundant_root_frontier_imports() -> anyhow::Result<()> {
let (bytes, shallow_root, _) = multi_frontier_shallow_snapshot()?;
let imported = LoroDoc::new();
imported.import(&bytes)?;
imported.set_detached_editing(true);

imported.checkout(&shallow_root)?;
imported.set_peer_id(3)?;
imported.get_text("tail").insert(0, "tail")?;
imported.commit();
let tail = imported.state_frontiers();
let expected = imported.get_deep_value();

let mut redundant_target = tail.clone();
redundant_target.push(shallow_root.iter().next().unwrap());
let minimized_target = imported
.minimize_frontiers(&redundant_target)
.expect("target should be reachable");
assert_ne!(minimized_target, redundant_target);

let snapshot = imported.export(ExportMode::shallow_snapshot(&redundant_target))?;
let imported_again = LoroDoc::new();
imported_again.import(&snapshot)?;

assert!(imported_again.is_shallow());
assert_eq!(imported_again.shallow_since_frontiers(), minimized_target);
assert_eq!(imported_again.get_deep_value(), expected);
assert!(imported_again.frontiers_to_vv(&minimized_target).is_some());
Ok(())
}

#[test]
fn shallow_doc_with_multi_frontier_root_can_export_concurrent_tail() -> anyhow::Result<()> {
let (bytes, shallow_root, _) = multi_frontier_shallow_snapshot()?;
Expand Down Expand Up @@ -337,12 +388,14 @@ fn shallow_doc_with_multi_frontier_root_can_export_concurrent_tail() -> anyhow::
.frontiers_to_vv(&target)
.expect("target should be included");
let tail_json = imported.export_json_updates(&root_vv, &target_vv);
assert_eq!(tail_json.start_version, shallow_root);
let json_updated_from_root = LoroDoc::new();
json_updated_from_root.import(&bytes)?;
json_updated_from_root.import_json_updates(tail_json)?;
assert_eq!(json_updated_from_root.get_deep_value(), expected);

let all_tail_json = imported.export_json_updates(&Default::default(), &target_vv);
assert_eq!(all_tail_json.start_version, shallow_root);
let json_all_updated_from_root = LoroDoc::new();
json_all_updated_from_root.import(&bytes)?;
json_all_updated_from_root.import_json_updates(all_tail_json)?;
Expand Down
Loading