Skip to content

fix: centralize key-path resolution in operation-to-patches#2341

Closed
christianhg wants to merge 2 commits intomainfrom
refactor/key-path-patches
Closed

fix: centralize key-path resolution in operation-to-patches#2341
christianhg wants to merge 2 commits intomainfrom
refactor/key-path-patches

Conversation

@christianhg
Copy link
Member

@christianhg christianhg commented Mar 9, 2026

Extracts a resolveKeyPath function that converts positional Slate paths to key-based Portable Text paths. Refactors operation-to-patches.ts to use it instead of inline key lookups scattered across five functions.


operation-to-patches.ts translates Slate operations into Portable Text patches for collaboration. Each of its five functions - insertTextPatch, removeTextPatch, setNodePatch, insertNodePatch, removeNodePatch - takes a Slate operation with a positional path like [0, 1] and needs to produce a patch with a key-based path like [{_key: 'blockKey'}, 'children', {_key: 'spanKey'}, 'text']. Every function does this by reaching into the tree inline: children[operation.path[0]!]._key, block.children[operation.path[1]!]._key. The same pattern repeated five times, each with its own error handling and edge cases.

This PR extracts that pattern into resolveKeyPath(schema, tree, slatePath), which walks the tree once and returns the key-based path. The patch functions call resolveKeyPath and then use small helpers (withTextField, withProperty) to append field names. setNodePatch is decomposed into setBlockNodePatch and setChildNodePatch. insertNodePatch is decomposed into insertBlockNodePatch and insertChildNodePatch.

The current implementation handles the two-level block/children structure. When containers land, resolveKeyPath generalizes to walk nested container fields using resolveArrayFields from the schema - the patch functions don't need to change.

Extracts a `resolveKeyPath` function that converts positional Slate paths
to key-based PT paths using the tree. Uses it in operation-to-patches.ts
to replace scattered inline key lookups.

This is the foundation for making operations and patches isomorphic —
when containers land, resolveKeyPath generalizes to walk nested container
fields, and the patch functions don't need to change.

- New: resolve-key-path.ts with resolveKeyPath() + KeyPath type
- Refactored: operation-to-patches.ts uses resolveKeyPath + path helpers
- New: 11 tests for resolveKeyPath covering blocks, children, objects, edge cases
- All 255 existing unit tests pass
@vercel
Copy link

vercel bot commented Mar 9, 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 9, 2026 9:09pm
portable-text-example-basic Ready Ready Preview, Comment Mar 9, 2026 9:09pm
portable-text-playground Ready Ready Preview, Comment Mar 9, 2026 9:09pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 9, 2026

🦋 Changeset detected

Latest commit: 3cbb0c2

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 Patch
@portabletext/plugin-character-pair-decorator Patch
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-markdown-shortcuts Patch
@portabletext/plugin-one-line Patch
@portabletext/plugin-paste-link Patch
@portabletext/plugin-sdk-value 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

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

📦 Bundle Stats — @portabletext/editor

Compared against main (d312abd5)

@portabletext/editor

Metric Value vs main (d312abd)
Internal (raw) 800.6 KB -458 B, -0.1%
Internal (gzip) 150.3 KB -27 B, -0.0%
Bundled (raw) 1.41 MB -458 B, -0.0%
Bundled (gzip) 313.2 KB -29 B, -0.0%
Import time 102ms +0ms, +0.3%

@portabletext/editor/behaviors

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

@portabletext/editor/plugins

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

@portabletext/editor/selectors

Metric Value vs main (d312abd)
Internal (raw) 60.2 KB -
Internal (gzip) 9.4 KB -
Bundled (raw) 56.7 KB -
Bundled (gzip) 8.6 KB -
Import time 10ms +0ms, +1.5%

@portabletext/editor/utils

Metric Value vs main (d312abd)
Internal (raw) 24.2 KB -
Internal (gzip) 4.7 KB -
Bundled (raw) 22.2 KB -
Bundled (gzip) 4.4 KB -
Import time 10ms -0ms, -0.5%
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Treemap artifacts are attached to the CI run for detailed size analysis
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

Extracts `resolveKeyPath` to convert positional Slate paths to key-based
PT paths. Refactors `operation-to-patches.ts` to use it instead of
scattered inline key lookups across five functions.
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