Skip to content

feat: support container normalization#2481

Merged
christianhg merged 1 commit intomainfrom
fix/container-normalization
Apr 9, 2026
Merged

feat: support container normalization#2481
christianhg merged 1 commit intomainfrom
fix/container-normalization

Conversation

@christianhg
Copy link
Copy Markdown
Member

@christianhg christianhg commented Apr 9, 2026

Editable containers need the same structural guarantees as text blocks: their child array field must exist and must not be empty. Without this, a cursor can never be placed inside a container, because there's no leaf node to anchor to.

The normalization is schema-driven and scope-aware. Container types use scoped dot-separated names in editableTypes (e.g., table, table.row, table.row.cell), but nodes in the tree only carry bare type names (row, cell). Every function that needs to identify a container type must reconstruct the scoped name by walking ancestor nodes. This applies to normalize-node.ts, modify.ts, and get-dirty-paths.ts.

getDirtyPaths is the critical piece. When an operation inserts or replaces nodes, their descendants need to be dirtied so the normalizer visits them. collectDescendantPaths threads a scopePrefix through recursion, building the scoped name at each level. For set_node (where the sync machine replaces a container's child array atomically), buildScopeContext walks the tree to the target node to recover the scope. Numeric indices are used throughout instead of keyed lookups, because pre-normalization nodes can have duplicate keys.

The normalization is gated by editableTypes, which is only populated when a renderer is registered. This PR includes the internal rendering pipeline so the normalization can be tested. The pipeline has no public API surface. Tests configure it by sending register renderer events directly to the editor actor.

When a renderer is registered after the editor is already loaded (late registration), syncEditableTypes calls normalize(force: true) to visit existing container nodes, then editor.onChange() to flow the changes back to the machine context.

15 browser tests cover: table and callout normalization cascades, text blocks with missing children inside containers, duplicate keys, void containers left untouched, incoming patches that unset container fields, remote value updates with deeply nested childless blocks, removing the last child from a container, and late renderer registration.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: 1ec7153

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

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 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 Apr 9, 2026 9:25am
portable-text-example-basic Ready Ready Preview, Comment Apr 9, 2026 9:25am
portable-text-playground Ready Ready Preview, Comment Apr 9, 2026 9:25am

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

📦 Bundle Stats — @portabletext/editor

Compared against main (3a751303)

@portabletext/editor

Metric Value vs main (3a75130)
Internal (raw) 765.4 KB +8.4 KB, +1.1%
Internal (gzip) 144.6 KB +1.6 KB, +1.1%
Bundled (raw) 1.36 MB +8.4 KB, +0.6%
Bundled (gzip) 305.1 KB +1.6 KB, +0.5%
Import time 94ms +0ms, +0.2%

@portabletext/editor/behaviors

Metric Value vs main (3a75130)
Internal (raw) 467 B -
Internal (gzip) 207 B -
Bundled (raw) 424 B -
Bundled (gzip) 171 B -
Import time 2ms -0ms, -1.7%

@portabletext/editor/plugins

Metric Value vs main (3a75130)
Internal (raw) 2.5 KB -
Internal (gzip) 910 B -
Bundled (raw) 2.3 KB -
Bundled (gzip) 839 B -
Import time 8ms -0ms, -0.2%

@portabletext/editor/selectors

Metric Value vs main (3a75130)
Internal (raw) 60.5 KB -
Internal (gzip) 9.5 KB -
Bundled (raw) 56.9 KB -
Bundled (gzip) 8.7 KB -
Import time 6ms -0ms, -1.8%

@portabletext/editor/utils

Metric Value vs main (3a75130)
Internal (raw) 24.2 KB -
Internal (gzip) 4.7 KB -
Bundled (raw) 22.2 KB -
Bundled (gzip) 4.4 KB -
Import time 6ms -0ms, -1.5%

🗺️ . · ./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 fix/container-normalization branch from de118c1 to 326d89e Compare April 9, 2026 05:55
@christianhg christianhg force-pushed the fix/container-normalization branch from 326d89e to 7dba465 Compare April 9, 2026 06:00
@christianhg christianhg force-pushed the fix/container-normalization branch from 7dba465 to d813b86 Compare April 9, 2026 06:01
@christianhg christianhg force-pushed the fix/container-normalization branch 2 times, most recently from cbaaa79 to 6d0b46b Compare April 9, 2026 06:17
@christianhg christianhg force-pushed the fix/container-normalization branch from 6d0b46b to 4ea2047 Compare April 9, 2026 06:19
@christianhg christianhg force-pushed the fix/container-normalization branch from e6a1343 to 1ec7153 Compare April 9, 2026 09:23
@christianhg christianhg changed the title fix: normalize container child array fields feat: support container normalization Apr 9, 2026
@christianhg christianhg merged commit 3201611 into main Apr 9, 2026
17 checks passed
@christianhg christianhg deleted the fix/container-normalization branch April 9, 2026 10:11
@ecoscript ecoscript bot mentioned this pull request Apr 8, 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