feat(plugin-sdk-value): sync through operational patches instead of whole-value diffs#2977
feat(plugin-sdk-value): sync through operational patches instead of whole-value diffs#2977ryanbonial wants to merge 4 commits into
Conversation
🦋 Changeset detectedLatest commit: 6e26dce The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
@ryanbonial is attempting to deploy a commit to the Sanity Team on Vercel. A member of the Team first needs to authorize it. |
…ng across clients
…server applier Content Lake never creates array items when applying a set: a keyed or indexed segment that does not resolve is a silent no-op (verified against gradient's jsonpath matcher, which only auto-creates missing object properties). The vendored applier mirrored an @sanity/sdk bug that fabricated a degenerate item instead, which made the mock server in the collision matrix more forgiving than the real backend. The SDK side of this is fixed in sanity-io/sdk#1059, where the fabricated items turned out to be the root cause of the 'Unexpected node type' crashes in the concurrency harness.
|
Update: root cause of the remaining harness crashes found, and it was in the SDK, not this plugin. The The commit just pushed here (6e26dce) is test-only: the vendored mock-server applier in the collision matrix mirrored the SDK's fabrication bug, making the mock more forgiving than the real backend. It now matches Content Lake (unresolvable keyed/indexed For harness verification of this PR, pair it with an SDK build that includes sanity-io/sdk#1059. |
Description
The plugin currently syncs by diffing whole-value snapshots in both directions, which makes concurrent edits to the same field last-writer-wins at best.
@sanity/sdk2.17.0 added an operational patch channel (remote-patchesdocument events on the read side,editDocument(..., {preserveOperations: true})on the write side, see sanity-io/sdk#1046), and this PR makes the plugin use it.SDKValuePluginsubscribes toremote-patchesevents, scopes document-rooted patches to the configured field, and applies them directly to the editor in every sync state. The editor merges them with in-flight local changes.preserveOperations, so keyed operations reach the server intact.@sanity/sdk-react< 2.17.0 the plugin behaves exactly as before.ValueSyncPlugingains optionalonRemotePatches/pushPatchesconfig for custom stores.Repair sync after best-effort patch application
Applying remote patches is best-effort: keyed operations produced by another client against a different view of the document (for example both clients splitting the same span to format overlapping ranges) can fail to resolve and are skipped, leaving the editor diverged from the stored document. The second commit closes that gap:
markDefs,marks), which the engine cannot resolve. Those are coalesced into whole-propertysetoperations taken from the target value.Testing
stringifyPatchPath,convertPatchesToSanity,scopeRemotePatches, and the newfindSidecarArrayPath/toEngineSafePatcheshelpers.applyAll, the whole-value fallback when patch pushing throws, and repair convergence when remote patches cannot apply.plugin.two-client.browser.test.tsx) connects two real editors through a mock server that applies patches best-effort and rebases each client's pending local changes, mirroring SDK optimistic behavior. It verifies convergence for concurrent typing in different blocks, concurrent same-range bolding, overlapping bold and link over the same range, and formatting conflicts during concurrent typing.SanityInstanceclients editing the same field through the Content Lake listener on@sanity/sdk-react2.17.0, with concurrent typing and same-range formatting converging on both sides.