Skip to content

fix: container-aware outgoing patches, uniform child field rule, and recursive normalization#2415

Closed
christianhg wants to merge 4 commits intomainfrom
refactor/operation-to-patches
Closed

fix: container-aware outgoing patches, uniform child field rule, and recursive normalization#2415
christianhg wants to merge 4 commits intomainfrom
refactor/operation-to-patches

Conversation

@christianhg
Copy link
Copy Markdown
Member

@christianhg christianhg commented Mar 25, 2026

Rewrites the outgoing patch pipeline (operation-to-patches.ts) to handle containers at any nesting depth. The old code hardcoded two levels (block at depth 0, child at depth 1) and threw on anything deeper. The new code uses indexedPathToKeyedPath for path conversion, walking the tree via the schema at each level. Each function takes a PatchContext (same shape as the traversal context: schema, editableTypes, value) and uses getNode and getChildren from the traversal layer for tree lookups. resolveParentFieldPath builds the keyed path to a node's parent including the schema-defined child field name, needed by insert patches to construct positional references.

The one special case is _key changes in setNodePatch, which must use positional paths since you can't reference a node by a key that's about to change. This derives the positional prefix by slicing the full keyed path rather than walking the tree a second time.

A second commit simplifies the applySetNode child field check: the text skip now uses isSpan directly instead of a roundabout isTextBlock parent check, and the children skip remains text-block-specific since container child fields must pass through to support incoming patches that set rows, cells, etc.

The third commit enforces a uniform child field rule in set_node: you cannot set any field that holds children, whether that's children on a text block or rows on a table. Both apply-operation.ts and applySetNode now resolve the child field name from the schema via resolveChildFieldName instead of hardcoding children. This makes the rule consistent across text blocks and containers.

The fourth commit adds recursive container normalization. When a container node has an empty or missing child array field, normalization builds the minimum valid structure from the schema in one pass. A table with no rows gets a row, which gets a cell, which gets a content block with a span. This works by recursively resolving child field definitions via resolveChildArrayFieldByType and building the full tree before inserting. The modify.ts utilities (modifyDescendant, modifyChildren) are also made container-aware so that insert_node and remove_node operations can target paths inside containers. modifyDescendant no longer depends on editableTypes for traversal, using schema-resolved child fields directly instead.

Container tests cover all five patch functions with a code block schema, the uniform child field rule with table set/unset operations, and normalization at every container depth (table, row, cell).

Note

All five outgoing patch functions now return empty arrays on failure instead of throwing. The editor runs in a racy DOM environment where transient path mismatches during React re-renders are normal, not bugs that should crash the component tree.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: 85210d4

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

This PR includes changesets to release 11 packages
Name Type
@portabletext/editor Minor
@portabletext/plugin-character-pair-decorator Major
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-markdown-shortcuts Major
@portabletext/plugin-one-line Major
@portabletext/plugin-paste-link Major
@portabletext/plugin-sdk-value Major
@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
Copy link
Copy Markdown

vercel bot commented Mar 25, 2026

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

Project Deployment Actions Updated (UTC)
portable-text-editor-documentation Ready Ready Preview, Comment Mar 28, 2026 7:18am
portable-text-example-basic Ready Ready Preview, Comment Mar 28, 2026 7:18am
portable-text-playground Ready Ready Preview, Comment Mar 28, 2026 7:18am

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

📦 Bundle Stats — @portabletext/editor

Compared against main (93a4f12d)

@portabletext/editor

Metric Value vs main (93a4f12)
Internal (raw) 766.4 KB +6.2 KB, +0.8%
Internal (gzip) 144.2 KB +1.2 KB, +0.9%
Bundled (raw) 1.37 MB +6.2 KB, +0.4%
Bundled (gzip) 307.1 KB +1.3 KB, +0.4%
Import time 97ms +2ms, +1.8%

@portabletext/editor/behaviors

Metric Value vs main (93a4f12)
Internal (raw) 467 B -
Internal (gzip) 207 B -
Bundled (raw) 424 B -
Bundled (gzip) 171 B -
Import time 6ms -0ms, -0.4%

@portabletext/editor/plugins

Metric Value vs main (93a4f12)
Internal (raw) 2.5 KB -
Internal (gzip) 910 B -
Bundled (raw) 2.3 KB -
Bundled (gzip) 839 B -
Import time 12ms +0ms, +1.9%

@portabletext/editor/selectors

Metric Value vs main (93a4f12)
Internal (raw) 60.5 KB -
Internal (gzip) 9.5 KB -
Bundled (raw) 56.9 KB -
Bundled (gzip) 8.7 KB -
Import time 10ms -0ms, -0.7%

@portabletext/editor/utils

Metric Value vs main (93a4f12)
Internal (raw) 24.2 KB -
Internal (gzip) 4.7 KB -
Bundled (raw) 22.2 KB -
Bundled (gzip) 4.4 KB -
Import time 9ms -0ms, -1.1%

🗺️ . · ./behaviors · ./plugins · ./selectors · ./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.

@christianhg christianhg force-pushed the refactor/operation-to-patches branch from d812174 to 5e23333 Compare March 25, 2026 08:31
@christianhg christianhg changed the title refactor: simplify operation-to-patches with indexedPathToKeyedPath fix: make operation-to-patches container-aware with indexedPathToKeyedPath Mar 25, 2026
@christianhg christianhg force-pushed the refactor/operation-to-patches branch from d676742 to 1a54577 Compare March 26, 2026 14:40
@christianhg christianhg changed the title fix: make operation-to-patches container-aware with indexedPathToKeyedPath fix: container-aware outgoing patches Mar 26, 2026
@christianhg christianhg force-pushed the refactor/operation-to-patches branch from 1a54577 to 27328e6 Compare March 26, 2026 15:01
@christianhg christianhg changed the title fix: container-aware outgoing patches fix: container-aware outgoing patches, uniform child field rule, and recursive normalization Mar 27, 2026
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