Skip to content

Fix JSX runtime for nested sibling children#7491

Open
bdbch wants to merge 5 commits intomainfrom
fervent-blackburn
Open

Fix JSX runtime for nested sibling children#7491
bdbch wants to merge 5 commits intomainfrom
fervent-blackburn

Conversation

@bdbch
Copy link
Member

@bdbch bdbch commented Feb 2, 2026

Changes Overview

Fixed JSX runtime to properly render nested sibling elements. The JSX h function now correctly spreads children arrays into the DOMOutputSpec structure required by ProseMirror, resolving the "Invalid array passed to renderSpec" error when using multiple sibling elements in custom node rendering.

Implementation Approach

Modified the JSX runtime's h function in packages/core/src/jsx-runtime.ts to:

  1. Detect when children is an array of multiple elements vs a single DOMOutputSpecArray
  2. Spread children arrays using the spread operator to create proper ProseMirror DOMOutputSpec structures
  3. Handle edge cases (empty arrays, null/undefined filtering)
  4. Maintain backward compatibility with existing single-child and non-array usage

The fix distinguishes between:

  • An array OF children [child1, child2] → spreads into parent
  • A single child that IS an array ['span', {}, 'text'] → keeps as single child

Testing Done

Created comprehensive test suite (packages/core/__tests__/jsx-runtime.spec.ts) with 27 tests covering:

  • Basic JSX functionality (tags, attributes, slots, function components)
  • Single child rendering
  • Multiple sibling rendering (the bug scenario)
  • Edge cases (empty arrays, null/undefined handling)
  • Fragment component integration
  • Content holes (slot tags)
  • Complex real-world scenarios

All tests pass, including the specific issue scenario:

// This now works correctly:
const statTitle = h('div', { class: 'stat-title', children: 'Title' })
const statValue = h('div', { class: 'stat-value', children: '1,000' })
const stat = h('div', { class: 'stat', children: [statTitle, statValue] })
const result = h('div', { class: 'stats', children: [stat] })

// Result: proper DOMOutputSpec with siblings spread

Verification Steps

  1. Run the new test suite: pnpm test:unit jsx-runtime.spec.ts - all 27 tests should pass
  2. Run full test suite: pnpm test:unit - all 597 tests should pass (no regressions)
  3. Test with the issue example:
    • Create a custom node with nested siblings using JSX syntax
    • Verify no "Invalid array passed to renderSpec" error occurs
    • Verify HTML renders correctly with properly nested structure

Additional Notes

This fix resolves GitHub issue #6949. The implementation maintains full backward compatibility - all existing code continues to work as before, while new code with multiple siblings now works correctly.

The key insight was distinguishing between a single DOMOutputSpecArray child (which always starts with a string tag) and an array of multiple children (which contains multiple DOMOutputSpecArray elements).

Checklist

  • I have created a changeset for this PR if necessary.
  • My changes do not break the library.
  • I have added tests where applicable.
  • I have followed the project guidelines.
  • I have fixed any lint issues.

Related Issues

Fixes #6949

Spread children arrays into the returned DOMOutputSpec so sibling
elements are spread as separate children instead of nested arrays.
Treat a single DOMOutputSpec array (starts with a string) as a single
child, filter out null/undefined entries, and treat empty arrays as
no children.
Add unit tests covering nested siblings and edge cases, and include
a changeset.
Copilot AI review requested due to automatic review settings February 2, 2026 20:02
@netlify
Copy link

netlify bot commented Feb 2, 2026

Deploy Preview for tiptap-embed ready!

Name Link
🔨 Latest commit 3a1adf5
🔍 Latest deploy log https://app.netlify.com/projects/tiptap-embed/deploys/69832cbf775eab0008a9b25e
😎 Deploy Preview https://deploy-preview-7491--tiptap-embed.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot
Copy link

changeset-bot bot commented Feb 2, 2026

🦋 Changeset detected

Latest commit: 3a1adf5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 72 packages
Name Type
@tiptap/core Patch
@tiptap/extension-audio Patch
@tiptap/extension-blockquote Patch
@tiptap/extension-bold Patch
@tiptap/extension-bubble-menu Patch
@tiptap/extension-code-block-lowlight Patch
@tiptap/extension-code-block Patch
@tiptap/extension-code Patch
@tiptap/extension-collaboration-caret Patch
@tiptap/extension-collaboration Patch
@tiptap/extension-details Patch
@tiptap/extension-document Patch
@tiptap/extension-drag-handle Patch
@tiptap/extension-emoji Patch
@tiptap/extension-file-handler Patch
@tiptap/extension-floating-menu Patch
@tiptap/extension-hard-break Patch
@tiptap/extension-heading Patch
@tiptap/extension-highlight Patch
@tiptap/extension-horizontal-rule Patch
@tiptap/extension-image Patch
@tiptap/extension-invisible-characters Patch
@tiptap/extension-italic Patch
@tiptap/extension-link Patch
@tiptap/extension-list Patch
@tiptap/extension-mathematics Patch
@tiptap/extension-mention Patch
@tiptap/extension-node-range Patch
@tiptap/extension-paragraph Patch
@tiptap/extension-strike Patch
@tiptap/extension-subscript Patch
@tiptap/extension-superscript Patch
@tiptap/extension-table-of-contents Patch
@tiptap/extension-table Patch
@tiptap/extension-text-align Patch
@tiptap/extension-text-style Patch
@tiptap/extension-text Patch
@tiptap/extension-twitch Patch
@tiptap/extension-typography Patch
@tiptap/extension-underline Patch
@tiptap/extension-unique-id Patch
@tiptap/extension-youtube Patch
@tiptap/extensions Patch
@tiptap/html Patch
@tiptap/markdown Patch
@tiptap/react Patch
@tiptap/starter-kit Patch
@tiptap/static-renderer Patch
@tiptap/suggestion Patch
@tiptap/vue-2 Patch
@tiptap/vue-3 Patch
@tiptap/extension-drag-handle-react Patch
@tiptap/extension-drag-handle-vue-2 Patch
@tiptap/extension-drag-handle-vue-3 Patch
@tiptap/extension-bullet-list Patch
@tiptap/extension-ordered-list Patch
@tiptap/extension-list-item Patch
@tiptap/extension-list-keymap Patch
@tiptap/extension-task-item Patch
@tiptap/extension-task-list Patch
@tiptap/extension-table-cell Patch
@tiptap/extension-table-header Patch
@tiptap/extension-table-row Patch
@tiptap/extension-color Patch
@tiptap/extension-font-family Patch
@tiptap/extension-character-count Patch
@tiptap/extension-dropcursor Patch
@tiptap/extension-focus Patch
@tiptap/extension-gapcursor Patch
@tiptap/extension-history Patch
@tiptap/extension-placeholder Patch
@tiptap/pm 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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug in the JSX runtime where nested sibling elements were not properly rendered, causing ProseMirror's "Invalid array passed to renderSpec" error. The fix modifies the h function to correctly spread children arrays into the DOMOutputSpec structure.

Changes:

  • Enhanced JSX runtime to detect and handle multiple sibling children by spreading them into the parent's DOMOutputSpec
  • Added comprehensive test suite with 27 tests covering various JSX rendering scenarios
  • Created changeset documenting the bug fix

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/core/src/jsx-runtime.ts Modified h function to properly spread children arrays, distinguishing between single DOMOutputSpecArray children and arrays of multiple children
packages/core/tests/jsx-runtime.spec.ts Added comprehensive test suite covering basic functionality, single/multiple children, edge cases, fragments, slots, and real-world scenarios
.changeset/fix-jsx-runtime-nested-siblings.md Added changeset documenting the patch-level bug fix for nested sibling rendering

Add tests covering DOMOutputSpec edge cases: tag+0, tag+attrs+0,
array of DOMOutputSpec arrays, and nested child DOMOutputSpec. Update
h() detection to treat the second element as valid when it is
undefined, 0, an attrs object, or a nested DOMOutputSpecArray.
@bdbch bdbch requested a review from Copilot February 2, 2026 20:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 4, 2026

Open in StackBlitz

@tiptap/extension-character-count

npm i https://pkg.pr.new/@tiptap/extension-character-count@7491

@tiptap/extension-dropcursor

npm i https://pkg.pr.new/@tiptap/extension-dropcursor@7491

@tiptap/extension-focus

npm i https://pkg.pr.new/@tiptap/extension-focus@7491

@tiptap/extension-history

npm i https://pkg.pr.new/@tiptap/extension-history@7491

@tiptap/extension-gapcursor

npm i https://pkg.pr.new/@tiptap/extension-gapcursor@7491

@tiptap/extension-list-item

npm i https://pkg.pr.new/@tiptap/extension-list-item@7491

@tiptap/extension-list-keymap

npm i https://pkg.pr.new/@tiptap/extension-list-keymap@7491

@tiptap/extension-placeholder

npm i https://pkg.pr.new/@tiptap/extension-placeholder@7491

@tiptap/extension-table-cell

npm i https://pkg.pr.new/@tiptap/extension-table-cell@7491

@tiptap/extension-table-header

npm i https://pkg.pr.new/@tiptap/extension-table-header@7491

@tiptap/extension-table-row

npm i https://pkg.pr.new/@tiptap/extension-table-row@7491

@tiptap/extension-task-item

npm i https://pkg.pr.new/@tiptap/extension-task-item@7491

@tiptap/extension-task-list

npm i https://pkg.pr.new/@tiptap/extension-task-list@7491

@tiptap/core

npm i https://pkg.pr.new/@tiptap/core@7491

@tiptap/extension-audio

npm i https://pkg.pr.new/@tiptap/extension-audio@7491

@tiptap/extension-blockquote

npm i https://pkg.pr.new/@tiptap/extension-blockquote@7491

@tiptap/extension-bubble-menu

npm i https://pkg.pr.new/@tiptap/extension-bubble-menu@7491

@tiptap/extension-bold

npm i https://pkg.pr.new/@tiptap/extension-bold@7491

@tiptap/extension-bullet-list

npm i https://pkg.pr.new/@tiptap/extension-bullet-list@7491

@tiptap/extension-code

npm i https://pkg.pr.new/@tiptap/extension-code@7491

@tiptap/extension-code-block

npm i https://pkg.pr.new/@tiptap/extension-code-block@7491

@tiptap/extension-code-block-lowlight

npm i https://pkg.pr.new/@tiptap/extension-code-block-lowlight@7491

@tiptap/extension-collaboration

npm i https://pkg.pr.new/@tiptap/extension-collaboration@7491

@tiptap/extension-collaboration-caret

npm i https://pkg.pr.new/@tiptap/extension-collaboration-caret@7491

@tiptap/extension-color

npm i https://pkg.pr.new/@tiptap/extension-color@7491

@tiptap/extension-document

npm i https://pkg.pr.new/@tiptap/extension-document@7491

@tiptap/extension-details

npm i https://pkg.pr.new/@tiptap/extension-details@7491

@tiptap/extension-drag-handle

npm i https://pkg.pr.new/@tiptap/extension-drag-handle@7491

@tiptap/extension-drag-handle-react

npm i https://pkg.pr.new/@tiptap/extension-drag-handle-react@7491

@tiptap/extension-drag-handle-vue-2

npm i https://pkg.pr.new/@tiptap/extension-drag-handle-vue-2@7491

@tiptap/extension-drag-handle-vue-3

npm i https://pkg.pr.new/@tiptap/extension-drag-handle-vue-3@7491

@tiptap/extension-emoji

npm i https://pkg.pr.new/@tiptap/extension-emoji@7491

@tiptap/extension-file-handler

npm i https://pkg.pr.new/@tiptap/extension-file-handler@7491

@tiptap/extension-floating-menu

npm i https://pkg.pr.new/@tiptap/extension-floating-menu@7491

@tiptap/extension-font-family

npm i https://pkg.pr.new/@tiptap/extension-font-family@7491

@tiptap/extension-hard-break

npm i https://pkg.pr.new/@tiptap/extension-hard-break@7491

@tiptap/extension-heading

npm i https://pkg.pr.new/@tiptap/extension-heading@7491

@tiptap/extension-highlight

npm i https://pkg.pr.new/@tiptap/extension-highlight@7491

@tiptap/extension-image

npm i https://pkg.pr.new/@tiptap/extension-image@7491

@tiptap/extension-horizontal-rule

npm i https://pkg.pr.new/@tiptap/extension-horizontal-rule@7491

@tiptap/extension-invisible-characters

npm i https://pkg.pr.new/@tiptap/extension-invisible-characters@7491

@tiptap/extension-italic

npm i https://pkg.pr.new/@tiptap/extension-italic@7491

@tiptap/extension-link

npm i https://pkg.pr.new/@tiptap/extension-link@7491

@tiptap/extension-list

npm i https://pkg.pr.new/@tiptap/extension-list@7491

@tiptap/extension-mathematics

npm i https://pkg.pr.new/@tiptap/extension-mathematics@7491

@tiptap/extension-mention

npm i https://pkg.pr.new/@tiptap/extension-mention@7491

@tiptap/extension-node-range

npm i https://pkg.pr.new/@tiptap/extension-node-range@7491

@tiptap/extension-paragraph

npm i https://pkg.pr.new/@tiptap/extension-paragraph@7491

@tiptap/extension-ordered-list

npm i https://pkg.pr.new/@tiptap/extension-ordered-list@7491

@tiptap/extension-strike

npm i https://pkg.pr.new/@tiptap/extension-strike@7491

@tiptap/extension-subscript

npm i https://pkg.pr.new/@tiptap/extension-subscript@7491

@tiptap/extension-superscript

npm i https://pkg.pr.new/@tiptap/extension-superscript@7491

@tiptap/extension-table

npm i https://pkg.pr.new/@tiptap/extension-table@7491

@tiptap/extension-table-of-contents

npm i https://pkg.pr.new/@tiptap/extension-table-of-contents@7491

@tiptap/extension-text

npm i https://pkg.pr.new/@tiptap/extension-text@7491

@tiptap/extension-text-align

npm i https://pkg.pr.new/@tiptap/extension-text-align@7491

@tiptap/extension-text-style

npm i https://pkg.pr.new/@tiptap/extension-text-style@7491

@tiptap/extension-twitch

npm i https://pkg.pr.new/@tiptap/extension-twitch@7491

@tiptap/extension-underline

npm i https://pkg.pr.new/@tiptap/extension-underline@7491

@tiptap/extension-typography

npm i https://pkg.pr.new/@tiptap/extension-typography@7491

@tiptap/extension-unique-id

npm i https://pkg.pr.new/@tiptap/extension-unique-id@7491

@tiptap/extension-youtube

npm i https://pkg.pr.new/@tiptap/extension-youtube@7491

@tiptap/extensions

npm i https://pkg.pr.new/@tiptap/extensions@7491

@tiptap/html

npm i https://pkg.pr.new/@tiptap/html@7491

@tiptap/markdown

npm i https://pkg.pr.new/@tiptap/markdown@7491

@tiptap/react

npm i https://pkg.pr.new/@tiptap/react@7491

@tiptap/starter-kit

npm i https://pkg.pr.new/@tiptap/starter-kit@7491

@tiptap/pm

npm i https://pkg.pr.new/@tiptap/pm@7491

@tiptap/static-renderer

npm i https://pkg.pr.new/@tiptap/static-renderer@7491

@tiptap/suggestion

npm i https://pkg.pr.new/@tiptap/suggestion@7491

@tiptap/vue-3

npm i https://pkg.pr.new/@tiptap/vue-3@7491

@tiptap/vue-2

npm i https://pkg.pr.new/@tiptap/vue-2@7491

commit: 3a1adf5

@bdbch bdbch changed the base branch from develop to main February 18, 2026 10:07
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.

JSX runtime doesn't render nested sibling elements correctly

2 participants