fix(compiler): AOT threads hydration id onto first element child of fragment roots [#2784]#2938
Merged
viniciusdacal merged 1 commit intomainfrom Apr 22, 2026
Merged
Conversation
4828d24 to
c964b2f
Compare
…ragment roots [#2784] Interactive components (with a `let` declaration) that returned a JSX fragment silently lost their `data-v-id` marker because `fragment_to_string` had no hydration parameter and `expr_to_string` passed the id into `element_to_string` but not into `fragment_to_string`. The client hydration walker then couldn't locate the component root, so event handlers and signal subscriptions never attached. Threads `hydration_id: Option<&str>` through `fragment_to_string` and `child_to_string`. Inside a fragment root, the id attaches to the first DOM element child; nested fragments recurse; text, expression, and component-tag children (which compile to `__ssr_Child({})` calls, not `__element(...)`) are skipped. Matches runtime SSR, where `inject_hydration_attr` already targets the first `__element(...)` and skips `document.createDocumentFragment()`. Covered by BDD scenarios for: element first child, text first child, nested fragment, single-element fragment, component first child, text/expression-only fragment, parenthesized fragment root, and a negative assertion that later siblings never receive the id. Closes #2784. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c964b2f to
691fd12
Compare
3 tasks
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.
Closes #2784.
Summary
Interactive components (with a
letdeclaration) that returned a JSX fragment silently lost theirdata-v-idmarker.fragment_to_stringhad nohydration_idparameter, andexpr_to_stringpassed the id intoelement_to_stringbut not intofragment_to_string. The client hydration walker then couldn't locate the component root, so event handlers and signal subscriptions never attached — an invisible failure for any component written asreturn <>...</>.Policy
Option (a) from the issue: propagate the hydration id onto the first JSX element child of the fragment. Chosen over option (b) (reject fragment roots from AOT) because the runtime SSR path already does this implicitly —
inject_hydration_attr(jsx_transformer.rs) uses a regex that skipsdocument.createDocumentFragment()and targets the firstconst __elN = __element("..."). This fix brings the AOT path in line with runtime SSR so the two never diverge.Changes
hydration_id: Option<&str>throughfragment_to_stringandchild_to_stringinnative/vertz-compiler-core/src/aot_string_transformer.rs.<Child/>compiles to__ssr_Child({})(not an element that can carry attributes), so attaching the id there would lose it entirely. This matches the runtime regex, which only matches__element(...).try_jsx_expr_to_string,expression_node_to_string,children_to_stringofcomponent_call_to_string) passNone— only the component root's fragment attaches the id.Tests (BDD)
Added 8 scenarios in
aot_string_transformer.rscovering every acceptance-criterion case from the issue plus the blockers surfaced in adversarial review:return (<>...</>)→ parens unwrapped, id still propagates.All 1192 compiler-core unit tests and 360 NAPI-binding integration tests pass. Clippy and rustfmt clean. Pre-push hooks green.
Public API Changes
None. Internal compiler fix; no TS-facing API change.
Test plan
cargo test -p vertz-compiler-core --lib(1192 passed)cargo test --all(all crates passed)cargo clippy --all-targets -- -D warnings(clean)cargo fmt --all -- --check(clean)bun test __tests__/*.test.tsinnative/vertz-compiler(360 passed)vtz test packages/ui-server/src/__tests__(978 passed)🤖 Generated with Claude Code