feat(portable-text): support container plugins via PTE catch-all renderers#12963
Draft
christianhg wants to merge 8 commits into
Draft
feat(portable-text): support container plugins via PTE catch-all renderers#12963christianhg wants to merge 8 commits into
christianhg wants to merge 8 commits into
Conversation
Brings in the `'*'` catch-all node registration sentinel
(`defineX({type: '*'})`) added in `@portabletext/editor@7.2.0`,
required by the Compositor rewire.
Cascade-bumps the editor plugins to their matching .9 patches.
…erers
`@portabletext/editor@7.2.0` introduces a `'*'` catch-all sentinel
for the `defineX` node registration factories. Wiring three such
catch-alls through `<NodePlugin>` inside Compositor lets container
plugins compose with Studio's existing block/inline-object chrome,
list-item rendering, and style wrapping — without expanding Studio's
public API surface.
- `defineTextBlock({type: '*'})` — text blocks of any `_type`. The
catch-all wraps editable children in Studio's `<Style>` and
`<ListItem>` components since under `defineTextBlock` the engine
no longer applies its default style/list-item wrapping.
- `defineBlockObject({type: '*'})` — non-text blocks. Falls through
to the engine default when the type isn't enumerated by the
Sanity form-store walker (e.g. types declared inside a container's
`of` array).
- `defineInlineObject({type: '*'})` — inline objects. Same
fall-through behaviour for container-nested types.
Annotations remain on the legacy `renderAnnotation` callback;
the PTE engine fires it regardless of the new pipeline.
`usePortableTextMemberItemsFromProps` previously enumerated form member items at a fixed depth — text blocks at the array's top level, plus `children` (inline objects) and `markDefs` (annotations) inside each text block. Container API v2 lets a block-object declare an `arrayField` (`list.items`, `list-item.content`, …) that contains more Portable Text nodes; objects living inside those nested arrays were not enumerated, so clicking an image inside a list item never opened its edit dialog. Extract the per-block branch as a recursive `collectFromBlockItem` that walks every array-of-objects field a block-object declares, collecting nested block-objects, text blocks, inline objects, and annotations at any depth.
`buildRangeDecorationSelectionsFromComments` and `buildTextSelectionFromFragment` previously located a comment's target text block via root-level `value.find(b => b._key === ...)`. With Container API v2 a text block may live inside a container (e.g. `list.items[].content[]`); the root-level find misses it and comments appear unlinked from their target text. Extract a shared `findTextBlockByKey` walker that descends into every array-valued field on each item, returning the block plus the full keyed path from the root for use as a `RangeDecoration` selection path. Pair it with `getEnclosingTextBlockKey` for the fragment helper, which used to read `path[0]._key` and now walks the path finding the keyed segment immediately before the `'children'` string segment.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Contributor
❌ E2E Tests🟢 199 passed • 🔴 2 failed • 🟡 5 flaky • (⚪ 90 skipped) • view full report • view run Debug failing tests locallySANITY_E2E_PROJECT_ID=ittbm412 \
SANITY_E2E_BASE_URL=https://e2e-studio-5sao25ciu.sanity.dev \
SANITY_E2E_DATASET=pr-12963-chromium-27009344885 \
SANITY_E2E_DATASET_CHROMIUM=pr-12963-chromium-27009344885 \
SANITY_E2E_DATASET_FIREFOX=pr-12963-firefox-27009344885 \
pnpm test:e2e --headed \
e2e/tests/pte/referencesInPopover.spec.ts |
Contributor
📦 Bundle Stats —
|
| Metric | Value | vs next-major (3bb48ed) | vs v5.30.0 |
|---|---|---|---|
| Internal (raw) | 4.55 MB | +1.8 KB, +0.0% | +4.2 KB, +0.1% |
| Internal (gzip) | 1.05 MB | +792 B, +0.1% | +1.4 KB, +0.1% |
| Bundled (raw) | 12.28 MB | +16.6 KB, +0.1% | -47.7 KB, -0.4% |
| Bundled (gzip) | 2.76 MB | +2.1 KB, +0.1% | -11.0 KB, -0.4% |
| Import time | 1.55s | -8ms, -0.5% | +60ms, +4.0% |
bin:sanity
| Metric | Value | vs next-major (3bb48ed) | vs v5.30.0 |
|---|---|---|---|
| Internal (raw) | 7.1 KB | - | - |
| Internal (gzip) | 2.9 KB | - | - |
| Bundled (raw) | 7.1 KB | - | - |
| Bundled (gzip) | 2.8 KB | - | - |
| Import time | 5ms | -0ms, -0.4% | +0ms, +6.2% |
🗺️ View treemap · 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.
Contributor
📚 TypeDoc Generation Result✅ TypeDoc generated successfully!
The TypeDoc JSON file has been generated and validated. All documentation scripts completed successfully. |
Contributor
⚡️ Editor Performance ReportUpdated Fri, 05 Jun 2026 10:26:27 GMT
Detailed information🏠 Reference resultThe performance result of
🧪 Experiment resultThe performance result of this branch
📚 Glossary
|
Contributor
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.
Description
Studio's PortableText input has rendered text blocks, block objects, inline objects, list items and styles through four legacy
PortableTextEditablecallbacks (renderBlock,renderChild,renderListItem,renderStyle). With@portabletext/editor@7.2.0the engine introduces a'*'catch-all sentinel for thedefineXnode registration factories —defineTextBlock({type: '*'}),defineBlockObject({type: '*'}),defineInlineObject({type: '*'})— that fires on any_typenot claimed by a more specific registration upstream.Rewire
Compositorto mount three such catch-alls through aNodePluginand drop the four legacy callbacks fromPortableTextEditable. The catch-all renders close over Compositor's locals directly; no new public Studio API.This unlocks container plugins (lists, tables, blockquotes, …) for end users. A consumer can now register a container plugin on a Portable Text array and it composes naturally with Studio's chrome — the form-store, comments, presence and edit dialogs work for objects nested inside the container, and any
_typethe consumer doesn't claim falls through to Studio's catch-all renders.Bonus: walk container-nested members in the form-store and resolve text blocks at any depth in the inline-comments helpers — both required for container plugins to compose with comments and the click-to-open dialog at arbitrary nesting depth.
Annotations stay on the legacy
renderAnnotationcallback (the PTE engine fires it regardless of the new pipeline).What to review
packages/sanity/src/core/form/inputs/PortableText/Compositor.tsx— three newdefineX({type: '*', render})registrations replaceeditorRenderBlock+editorRenderChild. The text-block catch-all wraps editable children in the existingStyleandListItemcomponents itself, since underdefineTextBlockthe engine no longer applies its default style/list-item wrapping. The block-object and inline-object catch-alls fall through toprops.renderDefault(props)when the type isn't enumerated by the Sanity form-store walker (container-nested types declared in a container'sofarray).packages/sanity/src/core/form/inputs/PortableText/Editor.tsx— drops therenderBlock,renderChild,renderListItem,renderStyleprops.packages/sanity/src/core/form/inputs/PortableText/hooks/usePortableTextMembers.tsx— replaces the depth-2 enumeration with a recursivecollectFromBlockItemso objects inside container fields (list.items[].content[].image, …) get form member items.packages/sanity/src/core/comments/utils/inline-comments/findTextBlockByKey.tsplus the two helpers that use it — recursive walker for resolving a text block by key inside containers; pairs withgetEnclosingTextBlockKeyfor fragment-side path walking.Testing
pnpm check:types✅pnpm check:format✅pnpm exec eslint packages/sanity/src/core/form/inputs/PortableText/ packages/sanity/src/core/comments/utils/inline-comments/✅pnpm --filter sanity exec vitest run src/core/comments/__tests__/buildRangeDecorationSelectionsFromComments✅ (6 passed, 3 pre-existing skipped)Notes for release
N/A — internal rewire that enables container plugin composition. Aligns Studio with the PTE v7 render pipeline introduced in
@portabletext/editor@7.2.0.