Skip to content

fix(collaboration): render cross-references for late-joining clients (SD-2975)#3211

Open
luccas-harbour wants to merge 15 commits into
mainfrom
luccas/sd-2975-bug-cross-references-do-not-render-in-collaboration-mode
Open

fix(collaboration): render cross-references for late-joining clients (SD-2975)#3211
luccas-harbour wants to merge 15 commits into
mainfrom
luccas/sd-2975-bug-cross-references-do-not-render-in-collaboration-mode

Conversation

@luccas-harbour
Copy link
Copy Markdown
Contributor

Summary

Fixes SD-2975: cross-references (and citations) not rendering for collaborators who join an active room. Word-imported crossReference/citation field nodes carry cached result runs in their inline content, but the ProseMirror schema declares them as leaf atoms — so when y-prosemirror hydrated the shared Yjs XML for a late joiner, the cached children violated the schema and the node failed to materialize.

  • Strip cached children from atom nodes during Yjs hydration. New normalize-yjs-fragment.js walks the supereditor XML fragment and clears children of crossReference/citation XmlElements. Runs before yXmlFragmentToProseMirrorRootNode and via observeDeep for incoming remote events. Runs inside a Yjs transaction with a private origin symbol so the normalizer ignores its own events.
  • Fall back to resolvedText on export. Once cached content is stripped, the editor still needs to round-trip something to DOCX. New buildFieldResultRuns shared helper rebuilds a w:r with the node's resolvedText attr when node.content is empty, applying xml:space="preserve" for boundary whitespace. Both crossReference-translator.js and citation-translator.js now route through it.
  • Editor lifecycle: OpenOptions accepts a fragment (Y.js XmlFragment) so callers can hydrate directly from a seeded room; the option is cleared on close to prevent stale fragments leaking across reopens. Pre-hydration normalization is also called in Editor.ts.
  • Tests cover hydration of a seeded crossReference from a Yjs room, normalizer cleanup on extension teardown, scoping of the normalizer's own transactions, citation atom-content normalization, and export fallback when cached content has been stripped.

Adds an Editor.collaboration-seed regression test that seeds a doc with a
crossReference node, syncs it to a Yjs fragment (including a cached-result
run), and verifies a second client hydrating from the room preserves the
node's instruction, target, and resolvedText attrs.
…gment

Imported Word cross references can carry cached result runs in the shared
Yjs XML, but the ProseMirror crossReference node is a leaf atom. When
y-prosemirror hydrated the fragment, those extra children caused the
node to fail schema validation and not render in collaboration mode.

Add normalizeYjsFragmentForSchema to clear children of any crossReference
elements before hydration, and invoke it both when creating the sync
plugin and when seeding the doc from a fragment in Editor.init.
@luccas-harbour luccas-harbour requested a review from a team as a code owner May 8, 2026 17:11
@linear
Copy link
Copy Markdown

linear Bot commented May 8, 2026

SD-2975

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

I wasn't granted permission to call the ecma-spec MCP tools, so I'm reviewing against ECMA-376 from working knowledge. The code changes are small enough to assess directly.

Status: PASS

The new buildFieldResultRuns helper in build-field-result-runs.js emits a standard w:r containing w:rPr followed by w:t, which is exactly the shape ECMA-376 prescribes for CT_R (w:r, w:t). Specifically:

  • Child order is correct — w:rPr precedes the run content (w:t), as required by CT_R.
  • xml:space="preserve" is conditionally applied to w:t only when resolvedText has leading/trailing whitespace, matching the W3C convention OOXML inherits.
  • w:rPr is always emitted (even with empty outputMarks), but an empty <w:rPr/> is valid per the schema.
  • \u200e (LRM) in the test fixture is correctly not treated as whitespace — it's a directional formatting char, so omitting xml:space="preserve" is right.

The two translator changes (citation-translator.js, crossReference-translator.js) just delegate to the shared helper; their existing call to buildInstructionElements for the REF/citation instructions is unchanged, and the w:fldChar/w:instrText framing they emit elsewhere is not touched here. No new attributes or elements are introduced — the PR only adds a fallback rendering path using already-valid OOXML primitives.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a51835562

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/super-editor/src/editors/v1/core/Editor.ts Outdated
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants