Skip to content

Snapshot of a version with a mark's start but not its end panics on read ("unclosed style mark") #1165

Closed

Description

@zxch3n

Summary

A snapshot of a version that contains a mark's StyleStart op but not its StyleEnd op can't be decoded. The first read of the text panics in container_wrapper.rs:198 with DecodeError("Decode richtext state failed: unclosed style mark"), and the WASM instance is poisoned after that.

Such versions can only be reached with op-level frontiers, not commit frontiers: a mark writes StyleStart at counter c and StyleEnd at c + 1. checkout at such a version works; forkAt and snapshot export from it don't.

Repro (loro-crdt, built from main ce6498b)

const doc = new LoroDoc();
doc.setPeerId(1);
doc.configTextStyle({ bold: { expand: "after" } });
const text = doc.getText("t");
text.insert(0, "abc");                          // 0@1..2@1
text.mark({ start: 1, end: 3 }, "bold", true);  // StyleStart 3@1, StyleEnd 4@1
text.insert(3, "Z");                            // 5@1
doc.commit();

const fork = doc.forkAt([{ peer: "1", counter: 3 }]); // has the StyleStart, not the StyleEnd
fork.getText("t").toDelta();
// panicked at crates/loro-internal/src/state/container_store/container_wrapper.rs:198:37:
// called `Result::unwrap()` on an `Err` value: DecodeError("Decode richtext state failed: unclosed style mark")

The same panic happens after doc.checkout([{ peer: "1", counter: 3 }]); const bytes = doc.fork().export({ mode: "snapshot" }); when another document imports bytes and reads the text.

export({ mode: "update" }), state-only, and shallow-snapshot at that version work (a shallow snapshot moves its root to a critical version).

Expected

Either the snapshot writer closes the style or leaves it out, or the decoder accepts an unclosed start anchor (the style isn't applied until its end anchor is included, as checkout already shows), or import returns Err. It shouldn't panic on the first read.

Context

Found in the second review of #1136/#1137. Since #1137, loro.js writes this state in Rust's layout, so a loro.js snapshot of such a version makes Rust panic the same way. loro.js itself reads it back.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions