oficina / docs Public

Regenerate the vector that recorded the bug as the contract

wasm_trap_containment expected "plain <strong>bold</strong> plain" as
the PLAIN-TEXT rendering of a bold paragraph, and passed for as long as
fragment_text emitted markup.

generate-fragment.mjs put it there deliberately, and its comment says
why: a first version used toDelta (mark-free), it disagreed with the
real Rust output, and the disagreement was resolved by changing the
generator to match. The comment defends this — inline pseudo-tags are
"XmlText's own plain text convention in both implementations" — and
both halves of that sentence are true. The conclusion is not. The two
agreed because they share a library's convention, not because either
was right, and fragment_text's own doc comment says it exists to give
"the words without the markup".

So the generator walks toDelta again, the vector is regenerated, and
the comment now records the whole arc rather than half of it. The
update BYTES are unchanged — only the expectation moved.

Also: the embedded wasm, re-copied, since fragment.rs changed.

A generator "verified against the real output" is verified against the
code, not against what the code should do.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoVTH3jZqj9svgt2UD9t1p
Paul Campbell pushed by paul@keymail.dev 3cdbcf53ccfd671f54828bb40a7039a9b4f1a672 parent 8033139
3 files changed, +37 −14
  • fixtures/generate-fragment.mjs +36 −13
  • fixtures/vectors/fragment/formatted.expected.json +1 −1
  • internal/core/testdata/docs_core.wasm binary
diff --git a/fixtures/generate-fragment.mjs b/fixtures/generate-fragment.mjs
index 4a921e7..5ef0b1f 100644
--- a/fixtures/generate-fragment.mjs
+++ b/fixtures/generate-fragment.mjs
@@ -269,20 +269,41 @@ writeFileSync('vectors/fragment/sizes.bin', docWith(f => {
// Y.Text.
//
// plainTextOf mirrors core/src/fragment.rs's fragment_text: walk the
-// tree, concatenate each Y.XmlText's own rendering, and join top-level
+// tree, concatenate each Y.XmlText's characters, and join top-level
// block texts with a blank line.
//
-// Uses each Y.XmlText's own toString(), not toDelta() — verified
-// against the real Rust output, not assumed: fragment_text calls
-// yrs's XmlTextRef::get_string(txn), and yrs's own get_string embeds
-// formatting as inline pseudo-tags exactly the way Y.XmlText.toString()
-// does in JS ("plain <strong>bold</strong> plain", not "plain bold
-// plain") — this is XmlText's own "plain text" convention in both
-// implementations, not the HTML fragment_html builds via its own MARKS
-// table. A first version of this generator used toDelta() (mark-free)
-// and it disagreed with the real Rust output on the very first run —
-// core/tests/wasm_trap_containment.rs caught it immediately, which is
-// the whole reason this fixture set exists.
+// TODELTA, NOT toString, AND THIS LINE HAS BEEN BOTH — the history is
+// the point and is worth reading before changing it back.
+//
+// The first version of this generator used toDelta (mark-free). It
+// disagreed with the real Rust output immediately, and the disagreement
+// was resolved the wrong way round: the generator was changed to
+// toString to match Rust, under a comment explaining that inline
+// pseudo-tags are "XmlText's own plain text convention in both
+// implementations". Both halves of that sentence are true. The
+// conclusion drawn from it was not.
+//
+// fragment_text's own doc comment says what it is for: "the words
+// without the markup". It called yrs's XmlTextRef::get_string, which
+// serialises a formatted run's attributes as XML tags, so it returned
+// "plain <strong>bold</strong> plain" — markup, from the one renderer
+// whose entire job is to have none. Y.XmlText.toString does the same
+// thing in JS, so the two agreed. They agreed because they shared a
+// library's convention, not because either was right, and the
+// expected.json committed here recorded the wrong answer as the
+// contract.
+//
+// It was latent — Store.Text has no caller in a handler — and it
+// surfaced from a LINK, whose "<link href=…>" was long enough to notice
+// where "<strong>" had sat unnoticed since marks existed. fragment.rs's
+// node_text walks diff now; this walks toDelta, which is the same walk
+// in the other language; and the vectors were regenerated in the commit
+// that changed both.
+//
+// The lesson worth keeping: a generator that is "verified against the
+// real output" is verified against the code, not against what the code
+// should do. Two implementations agreeing is evidence about the
+// library they share.
function plainTextOf(doc) {
const f = doc.getXmlFragment('prosemirror')
const isBlock = (name) =>
@@ -291,7 +312,9 @@ function plainTextOf(doc) {
let cur = ''
function walk(node) {
if (node instanceof Y.XmlText) {
- cur += node.toString()
+ for (const op of node.toDelta()) {
+ if (typeof op.insert === 'string') cur += op.insert
+ }
} else if (node instanceof Y.XmlElement) {
for (const child of node.toArray()) walk(child)
if (isBlock(node.nodeName) && cur.length > 0) {
diff --git a/fixtures/vectors/fragment/formatted.expected.json b/fixtures/vectors/fragment/formatted.expected.json
index 00adb0d..e763dbd 100644
--- a/fixtures/vectors/fragment/formatted.expected.json
+++ b/fixtures/vectors/fragment/formatted.expected.json
@@ -1,3 +1,3 @@
{
- "text": "plain <strong>bold</strong> plain"
+ "text": "plain bold plain"
}
diff --git a/internal/core/testdata/docs_core.wasm b/internal/core/testdata/docs_core.wasm
index 98eadac..f7b77bc 100755
Binary files a/internal/core/testdata/docs_core.wasm and b/internal/core/testdata/docs_core.wasm differ