Skip to content

fix: crash-proof concurrent value sync on top of #2974#2975

Draft
snorrees wants to merge 3 commits into
fix-sidecar-array-remote-patchesfrom
fix/concurrent-crashproof-on-2974
Draft

fix: crash-proof concurrent value sync on top of #2974#2975
snorrees wants to merge 3 commits into
fix-sidecar-array-remote-patchesfrom
fix/concurrent-crashproof-on-2974

Conversation

@snorrees

@snorrees snorrees commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

This is the combined crx snapshot publish platform hotfix: Ryan Bonial's engine
routing fix (#2974) with our residual client-side crash-proofing stacked on top.

The PR is opened with base = fix-sidecar-array-remote-patches (the branch
behind #2974), so the diff shown here is only our residual — two fix commits
plus a changeset.

✅ Released under the crx dist-tag

The combined fix (this PR + #2974) is published to npm under the crx
dist-tag. This does not touch latest — it's a snapshot hotfix for affected
users to pin to today:

package version npm
@portabletext/editor 7.10.8-crx.0 https://www.npmjs.com/package/@portabletext/editor/v/7.10.8-crx.0
@portabletext/plugin-sdk-value 7.0.35-crx.0 https://www.npmjs.com/package/@portabletext/plugin-sdk-value/v/7.0.35-crx.0

Install:

npm i @portabletext/editor@crx @portabletext/plugin-sdk-value@crx

Verified end-to-end against real Content Lake with the published packages: two
tabs editing the same field concurrently (typing + toggling bold/link) → no crash,
no document corruption, tabs converge
. (See the SAFETY matrix in the repro repo:
crash-and-corrupt → recoverable silent-loss.)

Root cause — credit to #2974

#2974 (Ryan Bonial,
fix-sidecar-array-remote-patches) is the actual root-cause fix. Remote patches
produced by diffing tools address elements of sidecar arrays
(span.marks[0], span.marks[-1], block.markDefs[_key==...]). The engine
previously routed those through structural child insertion/removal, which:

  • threw Cannot apply an "unset" (node removal) operation ... because the node was not found, killing the consumer's sync; and
  • silently wrote a bogus children array onto spans on insert, corrupting the
    document once pushed back to the datastore.

#2974 makes the engine detect these non-structural paths and apply them as plain
data patches on the root block. That is the fix; everything below is defense in
depth layered on top of it.

Our residual (this PR's diff)

  • @portabletext/editor (sync-machine.ts): keyed updateBlock unsets are
    guarded against the live engine tree, and the updateValue try/catch is widened
    so a stale-key removal degrades to a safe re-sync instead of surfacing an
    unhandled rejection that freezes the sync actor. Some of the per-updateBlock
    unset guards now overlap with fix: apply remote patches addressing marks and markDefs elements as data patches #2974 and are intentionally kept as belt-and-braces.
  • @portabletext/plugin-sdk-value (plugin.sdk-value.tsx): arrayifyPath
    returns null instead of throwing on diff-patch paths it cannot convert (e.g.
    array slices produced when clearing multiple marks); convertPatches drops the
    unconvertible ops and flags the batch incomplete; applySync then falls back to
    an authoritative full value update / resync.

Scope / what this does NOT fix

  • It stops the crash and the document corruption under concurrent editing.
  • It does NOT fix the underlying SDK concurrency clobber — two editors
    racing on the same block can still overwrite each other's changes. That remains
    to be addressed separately.

Status

  • Draft. Do not merge / do not publish to latest.
  • Shipped as a snapshot under the crx dist-tag (see above) — the interim
    hotfix affected users can pin to now.
  • Supersedes the private sanity-labs/editor-crx-fix#1.
  • Tests green locally: editor browser (chromium) 1723 passed / 1 skipped; plugin
    unit 10 passed; plugin browser (chromium) 12 passed. pnpm build all packages green.

🤖 Generated with Claude Code

snorrees and others added 3 commits July 16, 2026 00:40
Guard updateBlock keyed unsets against the live engine tree and widen
the updateValue try/catch so a stale-key removal degrades to a safe
re-sync instead of an unhandled rejection that freezes the sync actor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f paths

arrayifyPath returns null instead of throwing on unexpected diff-patch
paths (e.g. array slices from clearing multiple marks); convertPatches
drops unconvertible ops and flags the batch incomplete; applySync then
falls back to an authoritative full value update / resync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Credits #2974 for the engine routing fix and documents our residual
(updateValue try/catch widening + arrayifyPath/slice resilience).
@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: faf811c

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-sdk-value 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-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

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
portable-text-example-basic Ready Ready Preview, Comment Jul 15, 2026 10:52pm
portable-text-playground Ready Ready Preview, Comment Jul 15, 2026 10:52pm

Request Review

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @portabletext/editor

Compared against fix-sidecar-array-remote-patches (57f64bf8)

@portabletext/editor

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 803.5 KB +1.2 KB, +0.1%
Internal (gzip) 153.9 KB +194 B, +0.1%
Bundled (raw) 1.41 MB +1.2 KB, +0.1%
Bundled (gzip) 318.2 KB +210 B, +0.1%
Import time 71ms +11ms, +17.6% ⚠️

@portabletext/editor/behaviors

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 467 B -
Internal (gzip) 207 B -
Bundled (raw) 424 B -
Bundled (gzip) 171 B -
Import time 1ms +0ms, +19.4% ⚠️

@portabletext/editor/plugins

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 2.7 KB -
Internal (gzip) 894 B -
Bundled (raw) 2.5 KB -
Bundled (gzip) 827 B -
Import time 5ms +1ms, +17.7% ⚠️

@portabletext/editor/selectors

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 81.2 KB -
Internal (gzip) 14.9 KB -
Bundled (raw) 76.9 KB -
Bundled (gzip) 13.8 KB -
Import time 5ms +0ms, +2.7%

@portabletext/editor/traversal

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 28.1 KB -
Internal (gzip) 5.6 KB -
Bundled (raw) 28.1 KB -
Bundled (gzip) 5.5 KB -
Import time 4ms +0ms, +7.8%

@portabletext/editor/utils

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 29.7 KB -
Internal (gzip) 6.2 KB -
Bundled (raw) 27.4 KB -
Bundled (gzip) 5.9 KB -
Import time 4ms +0ms, +2.9%

🗺️ . · ./behaviors · ./plugins · ./selectors · ./traversal · ./utils · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @portabletext/markdown

Compared against fix-sidecar-array-remote-patches (57f64bf8)

Metric Value vs fix-sidecar-array-remote-patches (57f64bf)
Internal (raw) 53.8 KB -
Internal (gzip) 9.8 KB -
Bundled (raw) 348.9 KB -
Bundled (gzip) 96.3 KB -
Import time 26ms +0ms, +1.8%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

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