Skip to content

fix: apply remote patches addressing marks and markDefs elements as data patches#2974

Open
ryanbonial wants to merge 1 commit into
portabletext:mainfrom
ryanbonial:fix-sidecar-array-remote-patches
Open

fix: apply remote patches addressing marks and markDefs elements as data patches#2974
ryanbonial wants to merge 1 commit into
portabletext:mainfrom
ryanbonial:fix-sidecar-array-remote-patches

Conversation

@ryanbonial

Copy link
Copy Markdown

Description

Remote patches produced by diffing tools (e.g. @sanity/diff-patch in @portabletext/plugin-sdk-value) can address elements of sidecar arrays: span.marks[0], span.marks[-1], block.markDefs[_key=="..."]. These paths end in a keyed or numeric segment just like structural node paths, so applyOperation routed them through structural child insertion/removal against the owning node's children.

The consequences in a two-editor session, whenever a collaborator toggles a decorator or annotation:

  • unset marks[0] / unset markDefs[_key=="..."] threw Cannot apply an "unset" (node removal) operation ... because the node was not found., killing the consumer's sync actor. The editor kept accepting keystrokes but stopped receiving remote changes until reload.
  • insert after marks[-1] silently wrote a bogus children: ['strong'] array onto the span. The corrupted span was then pushed back to the datastore and persisted.
  • set marks[0] and diffMatchPatch on marks[0] were silently ignored, so the editors drifted apart.

The fix teaches the engine to detect that such a path does not target the owning node's structural child array (targetsStructuralChildren) and to apply the operation as a plain data patch on the root block (applyOnRootBlock, via applyAll from @portabletext/patches), matching what the datastore computed. This is the same fallback pattern the set and property-unset handlers already use for markDefs property paths. An alternative of rewriting these patches in plugin-sdk-value was rejected: it would only cover SDK apps and leave the engine bug reachable from every other remote patch source.

What to review

  • packages/editor/src/engine/core/apply-operation.ts: the routing check and the data-patch fallback, including the set-based inverse computed for local sidecar unsets (remote patches skip inverses).
  • packages/editor/src/internal-utils/applyPatch.ts: the diffMatchPatch fallback for non-text string targets.
  • Whether getChildFieldName is the right structural-field oracle for the check.

Testing

  • packages/editor/tests/event.patches.sidecar-arrays.test.tsx: 6 regression tests using the exact patch shapes diffValue emits for decorator/annotation toggles. All 6 fail on main (three throw, three silently corrupt or no-op).
  • packages/plugin-sdk-value/src/plugin.value-sync.browser.test.tsx: 2 end-to-end tests driving ValueSyncPlugin through remote decorator and annotation toggles, covering the span split/merge + markDefs flow.
  • Full existing suites pass: editor chromium browser tests (1721), editor unit tests (852), plugin-sdk-value suites, typecheck, lint.

@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 57f64bf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@portabletext/editor Patch
@portabletext/plugin-character-pair-decorator Patch
@portabletext/plugin-dnd Patch
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-list-index Patch
@portabletext/plugin-markdown-shortcuts Patch
@portabletext/plugin-one-line Patch
@portabletext/plugin-paste-link Patch
@portabletext/plugin-sdk-value Patch
@portabletext/plugin-table Patch
@portabletext/plugin-typeahead-picker Patch
@portabletext/plugin-typography Patch
@portabletext/toolbar Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

@ryanbonial is attempting to deploy a commit to the Sanity Team on Vercel.

A member of the Team first needs to authorize it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant