fix(compiler,ui): wrap multi-child component children in DocumentFragment [#2821]#2827
Merged
Merged
Conversation
…agment [#2821] Previously, a component with multiple JSX children compiled to `Component({ children: () => [a, b] })`. Consumers such as `Context.Provider`, `Suspense`, and `ErrorBoundary` call `children()` and expect a single Node — they got an array, which downstream `appendChild` calls rejected with TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. The compiler now emits a `DocumentFragment`-returning thunk for multi-child components, mirroring how `<>...</>` fragments are already handled. `Context.Provider` also flattens any hand-written array result into a `DocumentFragment` (via `resolveChildren`) as a defensive fallback, replacing the previous dev-only throw — that check was unreliable in the browser because `process.env.NODE_ENV` is not polyfilled there. Closes #2821. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…2821] Two CI follow-ups for the fragment-children change: - The NAPI parity test in `native/vertz-compiler/__tests__/jsx-transform.test.ts` still asserted the old `children: () => [...]` array shape; update it to expect the `createDocumentFragment` thunk so it matches the Rust unit test. - `packages/ui/src/component/context.ts` now references `document` (via `createDocumentFragment` / `createTextNode`) inside the multi-child fallback. Add it to the `audit-window-document-refs.sh` allowlist with the rest of the client-only @vertz/ui rendering files — the references only run when `Provider`'s children thunk produces an array, which is always behind the JSX runtime / SSR DOM shim. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This was referenced Apr 19, 2026
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.
Summary
Closes #2821.
RouterContext.Provider(and any other component that treatschildrenas a renderable slot —Suspense,ErrorBoundary,ThemeProvider, …) crashed at mount when given multiple JSX children:Root cause: the compiler emitted
Component({ children: () => [a, b] })for multi-child components. Consumers calledchildren()expecting a singleNode, got an array, and downstreamappendChildrejected it. The dev-only guard inContext.Providerthat was supposed to produce a friendly error was gated onprocess.env.NODE_ENV, which is not polyfilled in the browser — so users saw the generic DOM error instead.Public API Changes
Fixed
DocumentFragment-returning thunk (same shape fragments use), so<Provider><a/><b/></Provider>behaves like<Provider><><a/><b/></></Provider>. No more "children must have a single root element" requirement.Context.Provider— array results from hand-written callers are flattened viaresolveChildreninto aDocumentFragment, instead of throwing. Replaces the unreliable dev-only throw.Breaking
None. The previous behavior was a crash; the new behavior is correct rendering.
Test plan
component_children_multiplenow assertscreateDocumentFragmentoutput; newcomponent_member_expression_multi_child_uses_fragmentreproduces the exactRouterContext.Providercase from router: RouterContext.Provider with multiple JSX children throws generic appendChild error #2821.null/undefined/booleanfiltered; direct (non-thunk) arrays; nested arrays and thunks flatten.Files
🤖 Generated with Claude Code