Skip to content

fix: remove internal compound operations completely#2346

Merged
christianhg merged 5 commits intomainfrom
refactor/nuke-compound-ops-v2
Mar 11, 2026
Merged

fix: remove internal compound operations completely#2346
christianhg merged 5 commits intomainfrom
refactor/nuke-compound-ops-v2

Conversation

@christianhg
Copy link
Member

@christianhg christianhg commented Mar 10, 2026

After #2327 decomposed split_node, merge_node, and move_node into lower-level operations, the compound types lingered in the Slate type system - Path.transform, Point.transform, Operation.inverse, and the Operation union all still handled cases that could never fire. This PR removes them entirely, inlines the transform logic that the two remaining apply helpers need, and deletes applyMoveNode along with the dead code paths it was keeping alive.


applyMergeNode and applySplitNode decompose compound operations into lower-level ones, but they still need the compound operation's ref-transform semantics: a merge shifts paths differently than the equivalent insert_text + remove_node sequence. Previously they delegated to Path.transform and Point.transform for this, which meant those functions had to keep the compound operation cases around even though no other code path used them. This PR inlines the transform logic directly into each helper as local transformPathForMerge, transformPointForMerge (and equivalents for split), then removes the compound types from Path.transform, Point.transform, Operation.inverse, operationCanTransformPath, the Operation union, and CustomTypes.

Path.transform drops from 130 lines to 30 - it now only handles insert_node and remove_node. Point.transform and Operation.inverse each lose three cases, including the most complex one (move_node inverse required transforming paths through the operation itself).

applyMoveNode is removed entirely. Its two call sites (operation.move.block.ts and delete-text.ts) now use plain remove_node + insert_node directly. The move helper's 180+ lines of ref pre-transform machinery had zero test coverage because the selection fixup it provided was redundant at both call sites: operation.move.block.ts already does manual selection fixup after the move, and delete-text.ts relies on applyMergeNode to handle selection immediately after.

The inlining surfaced a subtle detail in how RangeRef affinities are resolved. When a RangeRef has 'inward' affinity, the anchor and focus need different per-point affinities: on a forward range, the anchor gets 'forward' and the focus gets 'backward' (they contract toward each other). The old code got this for free through the Range.transform -> Point.transform chain. The inlined version needs it explicitly. A shared rangeRefAffinities helper resolves per-point affinities for applySplitNode - the only remaining helper where affinity matters, since merge point transforms don't depend on affinity.

Also cleans up dead code exposed by the removal: a split_node check in transformPendingPoint (diff-text.ts) that could never trigger, the unused properties parameter on applyMergeNode, dead inline-Element and block-level branches in applyInsertNodeAtPoint (impossible in the PT-native tree where inline objects are ObjectNodes, not Elements), and unused insert options on applyInsertNodeAtPath/applyInsertNodeAtPoint.

The operation vocabulary is now fully reduced: insert_node, remove_node, insert_text, remove_text, set_node, and set_selection. Two helpers remain (applySplitNode, applyMergeNode) that compose these into higher-level semantics with correct ref transforms. The next step is generalizing operation-to-patches.ts to use key-based paths for container support (#2336).

@vercel
Copy link

vercel bot commented Mar 10, 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 11, 2026 8:37am
portable-text-example-basic Ready Ready Preview, Comment Mar 11, 2026 8:37am
portable-text-playground Ready Ready Preview, Comment Mar 11, 2026 8:37am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

🦋 Changeset detected

Latest commit: 20650bf

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 10, 2026

📦 Bundle Stats — @portabletext/editor

Compared against main (d312abd5)

@portabletext/editor

Metric Value vs main (d312abd)
Internal (raw) 798.3 KB -2.7 KB, -0.3%
Internal (gzip) 150.0 KB -281 B, -0.2%
Bundled (raw) 1.40 MB -2.7 KB, -0.2%
Bundled (gzip) 313.0 KB -262 B, -0.1%
Import time 102ms +0ms, +0.4%

@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.8%

@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.6%

@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 11ms +0ms, +0.9%

@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, +1.0%
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.

Remove `merge_node`, `split_node`, and `move_node` from the internal Slate
`Operation` union, `Operation.inverse`, `Path.transform`, `Point.transform`,
and `operationCanTransformPath`. These compound operations are no longer
emitted through `editor.apply` (they were decomposed into lower-level
operations in the patch-compliant operations work).

The apply helpers (`applyMergeNode`, `applySplitNode`, `applyMoveNode`) still
construct virtual compound operations internally for ref pre-transformation,
but now inline the transform logic directly instead of delegating to
`Path.transform`/`Point.transform`.

Adds `rangeRefAffinities` helper to correctly resolve per-point affinity
for `RangeRef`s during inlined transforms (inward affinity on a forward
range needs `anchor=forward`, `focus=backward`).

Also removes dead `split_node` check in `diff-text.ts`.
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