# Repository Guidelines ## Project Snapshot Loro is a Rust CRDT workspace with JS/WASM packaging and a MoonBit codec. - `crates/loro`: public Rust API; avoid breaking downstream users. - `crates/loro-internal`: core CRDT logic. Read its [AGENTS.md](crates/loro-internal/AGENTS.md) before changing import/export, encoding, state, diff, checkout, or replay behavior. - `crates/loro-wasm`: `loro-crdt` WASM/TypeScript package. Read its [AGENTS.md](crates/loro-wasm/AGENTS.md) before changing bindings, exports, wrappers, or build scripts. - `crates/delta`, `crates/rle`, `crates/kv-store`, `crates/fractional_index`, `crates/loro-common`, and `packages/fractional-index`: shared primitives and packages. - `moon/`: MoonBit Loro binary codec; use [skills/moonbit/SKILL.md](skills/moonbit/SKILL.md). ## Context Index - Undo/redo item boundaries when remote edits make an inverse a no-op: [context/undo-history-boundaries.md](context/undo-history-boundaries.md). - Rust undo failure rollback and transaction writer serialization: [context/undo-atomicity.md](context/undo-atomicity.md). - Encoding/import/export modes, current vs outdated formats, shallow snapshots: [context/internal-encoding.md](context/internal-encoding.md). - Mergeable container model, marker/cid rules, tests, and common pitfalls: [context/mergeable-containers.md](context/mergeable-containers.md). - `import_batch` force-detach, batch-wide rollback scope, and the never-exit-detached invariant: [context/import-batch-atomicity.md](context/import-batch-atomicity.md). - Which root containers are visible (history-touched vs handle-acquired), and why replays materialize empty roots: [context/root-container-visibility.md](context/root-container-visibility.md). - Imports that reuse local op ids (a shared peer id): the known-history check, what counts as equal, and what it cannot catch: [context/import-peer-id-reuse.md](context/import-peer-id-reuse.md). - `apply_diff`/`revert_to` rollback of a failed batch, reused op ids, other threads: [context/apply-diff-atomicity.md](context/apply-diff-atomicity.md). - Movable-list `Move`/`Set` import validation, and why a move of a deleted element is applied rather than rejected: [context/movable-list-op-validation.md](context/movable-list-op-validation.md). - Shallow-snapshot dead-style redaction (null-only, both-expand exclusion, root-whitelisted latest state): [context/shallow-snapshot-style-redaction.md](context/shallow-snapshot-style-redaction.md). - Rich-text insert positions next to style anchors, the insert cursor cache, and delete `start_id`s: [context/richtext-insert-positions.md](context/richtext-insert-positions.md). - Tree diff cache transitions and why their lamport window comes from the two versions, not the replay base: [context/tree-checkout-window.md](context/tree-checkout-window.md). - Tree event invariants (revived subtrees get `Create`s) and the two event paths, diff-calculator vs raw ops: [context/tree-events.md](context/tree-events.md). - Deep tree traversal, snapshot order, and iterative result disposal: [context/tree-depth.md](context/tree-depth.md). - loro.js tree diff item order, index counting, apply resolution, and batch dry-run: [context/loro-js-tree-diff.md](context/loro-js-tree-diff.md). - loro.js concurrent tree moves, skipped cycles, replay, checkout, and undo: [context/loro-js-tree-moves.md](context/loro-js-tree-moves.md). - User-facing Loro usage, sync, editor integration, and performance guidance: [skills/loro/SKILL.md](skills/loro/SKILL.md). - Pure TypeScript runtime indexes, complexity contracts, benchmarks, and remaining gaps: [context/loro-js-performance.md](context/loro-js-performance.md). - loro.js vs Rust randomized Text differential tests and the Rust behaviors they work around: [context/loro-js-rust-differential.md](context/loro-js-rust-differential.md). - loro.js rich-text style anchors, entity positions, and Rust's insert/delete/mark position rules: [context/loro-js-richtext-anchors.md](context/loro-js-richtext-anchors.md). - loro.js multi-peer differential fuzzing against replay, checkout, event, and Rust/WASM oracles: [context/loro-js-differential-fuzz.md](context/loro-js-differential-fuzz.md). - loro.js MovableList model, the MovableList Rust differential suite (`loro-js/tests/differential/`), and known loro.js/Rust divergences: [context/loro-js-movable-list.md](context/loro-js-movable-list.md). - WASM panic/OOM reporting channels, `__wbindgen_start` glue invariant, and trap-testing recipes: [context/wasm-error-reporting.md](context/wasm-error-reporting.md). - WASM container id wrapper identity, lazy caching, and benchmark: [context/wasm-container-id-cache.md](context/wasm-container-id-cache.md). - Bounded decoded-value cache in `InnerStore` (second-chance FIFO, eviction safety contract, loro-dev/loro#1092): [context/container-value-cache.md](context/container-value-cache.md). - Deep plain value decoding, Drop, and WASM conversion stack safety: [context/deep-value-stack-safety.md](context/deep-value-stack-safety.md). - How the arena learns a container's parent (parsed changes, state KV, the op log's creator resolver), its locking (including `ChangeStore`'s lock order), and import rollback: [context/arena-parent-links.md](context/arena-parent-links.md). - Why a failed import's rollback keeps arena indices (DocState, caches and handlers are keyed by them) and what it drops instead: [context/failed-import-arena-indices.md](context/failed-import-arena-indices.md). - Which deletions `DocState::is_deleted` caches, and how tree/movable-list children are revived: [context/dead-container-cache.md](context/dead-container-cache.md). - Movable-list `apply_diff`/undo: delete + re-insert of a child becomes a move, cursor invariant, pitfalls: [context/movable-list-apply-diff.md](context/movable-list-apply-diff.md). - Unknown container types (newer-version containers): never created locally, `apply_diff`/undo pre-check, events, test forging: [context/unknown-containers.md](context/unknown-containers.md). - Why parsed change blocks keep text and list values out of doc-wide arenas (`ValueSlice`, tracker tables): [context/text-arena-growth.md](context/text-arena-growth.md). - User callbacks that panic (subscribers, pre-commit, local updates): no poisoned locks, lost events or hangs: [context/callback-panics.md](context/callback-panics.md). - Context backlog: [context/CONTEXT-GAPS.md](context/CONTEXT-GAPS.md). ## Commands - JS deps: `pnpm install --frozen-lockfile`. - Rust build/check/format/lint: `cargo build`, `cargo check -p loro-internal`, `cargo fmt --all`, `pnpm check`. - Rust tests: `pnpm test`; internal doctests: `cargo test -p loro-internal --doc`. - Loom: `pnpm test-loom`. - WASM: `pnpm release-wasm`, or `pnpm -C crates/loro-wasm build-dev`. - Bundlers after WASM packaging changes: `pnpm test-bundlers`; browser runtime: `pnpm --dir examples/bundler-smoke-tests run test:browser`. - Fractional-index TS: `pnpm test-fractional-index`. - Fuzz smoke: `pnpm run-fuzz-corpus`. - MoonBit codec, when `moon` is available: from `moon/`, run `moon check`, `moon test`, `moon fmt`. Use narrow checks first. Ask before broad fuzzing or long browser matrices. ## Working Rules - Start with `git status --short --branch`; treat uncommitted changes as user work unless you made them. - Before editing, read every `AGENTS.md` from root to target directory. Keep `CLAUDE.md` as a symlink to the nearest `AGENTS.md`. - Use `rg` / `rg --files` for search. - Public API changes in `loro` or `loro-crdt` should be backward-compatible when possible. Prefer new `try_*` APIs over breaking signatures. - Internal corruption should fail fast; invalid external input should return `Err`. Returning wrong state is worse than panicking on an impossible internal invariant. - Add regression tests near behavior: `crates/loro/tests`, `crates/loro-internal/tests`, module tests, `crates/loro-wasm/tests`, or `moon/loro_codec/*_test.mbt`. - Add a changeset for publishing behavior or package output changes. - Do not hand-edit generated WASM package output; regenerate it with package scripts. ## Self-Maintained Agent Context - Treat "why was that hard to find?" as a context bug. Add a nearby `AGENTS.md` pointer or a `context/` article, or append a line to [context/CONTEXT-GAPS.md](context/CONTEXT-GAPS.md). - Keep root context short. If an `AGENTS.md` grows past about 4000 characters, move detail into a linked `context/` article. - Header context articles with `Verified against code YYYY-MM-DD`, anchor claims to files/symbols, and link them from root plus the nearest per-directory `AGENTS.md`. - If code changes make an `AGENTS.md` or context article stale, update the docs in the same change. - When a commit needs non-obvious rationale, land that rationale in the nearest context file and keep the commit message as a pointer. ## Commit And PR Notes History uses short imperative commits, often prefixed by `fix:`, `test:`, `chore:`, or `refactor:`. PRs should include summary, rationale, validation, and linked issues or traces when relevant. - Structured WASM reads, node identity, ranges and performance responsibilities: [context/wasm-container-tree.md](context/wasm-container-tree.md).