feat: make the internal Slate tree Portable Text-native#2279
Merged
christianhg merged 10 commits intomainfrom Mar 6, 2026
Merged
feat: make the internal Slate tree Portable Text-native#2279christianhg merged 10 commits intomainfrom
christianhg merged 10 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: 3533195 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
76adaf6 to
5266f4e
Compare
5266f4e to
60d3a83
Compare
60d3a83 to
e7a1b66
Compare
c6adffb to
bb4092c
Compare
b295444 to
a345988
Compare
a345988 to
256be5b
Compare
snorrees
approved these changes
Mar 4, 2026
Contributor
📦 Bundle Stats —
|
| Metric | Value | vs main (a657a78) |
|---|---|---|
| Internal (raw) | 816.3 KB | -16.2 KB, -1.9% |
| Internal (gzip) | 152.5 KB | -3.0 KB, -2.0% |
| Bundled (raw) | 1.42 MB | -16.4 KB, -1.1% |
| Bundled (gzip) | 315.4 KB | -3.2 KB, -1.0% |
| Import time | 107ms | -1ms, -1.3% |
@portabletext/editor/behaviors
| Metric | Value | vs main (a657a78) |
|---|---|---|
| Internal (raw) | 467 B | - |
| Internal (gzip) | 207 B | - |
| Bundled (raw) | 424 B | - |
| Bundled (gzip) | 171 B | - |
| Import time | 7ms | -0ms, -0.0% |
@portabletext/editor/plugins
| Metric | Value | vs main (a657a78) |
|---|---|---|
| Internal (raw) | 2.5 KB | - |
| Internal (gzip) | 910 B | - |
| Bundled (raw) | 2.3 KB | - |
| Bundled (gzip) | 839 B | - |
| Import time | 13ms | +0ms, +3.2% |
@portabletext/editor/selectors
| Metric | Value | vs main (a657a78) |
|---|---|---|
| Internal (raw) | 60.2 KB | -17 B, -0.0% |
| Internal (gzip) | 9.4 KB | -4 B, -0.0% |
| Bundled (raw) | 56.7 KB | - |
| Bundled (gzip) | 8.6 KB | - |
| Import time | 11ms | +0ms, +4.1% |
@portabletext/editor/utils
| Metric | Value | vs main (a657a78) |
|---|---|---|
| Internal (raw) | 24.2 KB | -17 B, -0.1% |
| Internal (gzip) | 4.7 KB | -4 B, -0.1% |
| Bundled (raw) | 22.2 KB | - |
| Bundled (gzip) | 4.4 KB | - |
| Import time | 10ms | +0ms, +3.1% |
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.
The internal Slate tree now stores Portable Text nodes directly instead of wrapping them in Slate-specific structures. Block objects and inline objects are stored as-is (no synthetic children array, no `__inline` flag), and the value wrapper that translated between Slate and Portable Text on every operation is removed entirely. This introduces `ObjectNode` as a third node type alongside Element (text blocks) and Text (spans). Node identity checks (Text.isText, Element.isElement, Node.isObjectNode) are now schema-driven, using the type name from the editor schema instead of structural field checks.
`insertFragment` is dead code in the PTE architecture - paste goes through the behavior system (clipboard.paste → insert.blocks), never through `insertFragment`.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The editor maintained a parallel Portable Text value alongside the internal Slate tree, maintaining both on every operation and translating between the two. This translation layer was a source of bugs (divergence between the two trees, edge cases in the bridge logic) and unnecessary complexity. This PR removes it by making the internal tree structurally match Portable Text, so no translation is needed.
Node identity checks like
Text.isTextandElement.isElementare now schema-driven, using the type name from the editor schema instead of structural field checks. Block objects and inline objects are no longer Elements with syntheticchildren: [{text: ''}]— they're stored as-is in the tree as ObjectNodes, a new third node type alongside Element (text blocks) and Text (spans). ObjectNodes are rendered via a dedicated component with a zero-width spacer for DOM selection, replacing Slate's void element rendering.With the tree now in Portable Text format, the value wrapper that translated between Slate and PT on every operation (~620 lines) is no longer needed and is deleted. Several unused Slate transforms inherited from upstream are also removed.
This is also a prerequisite for container support. The old architecture couldn't support block objects with nested Portable Text content because the value wrapper would need a complete rewrite to translate nested trees, and the synthetic children array on block objects would conflict with real children. With the PT-native tree, a container is just a block object that becomes an Element with real Portable Text children, while non-container block objects stay as ObjectNodes. The schema-driven identity checks make this distinction possible without structural heuristics.