Skip to content

feat(react): support element template events#2589

Merged
Yradex merged 6 commits into
lynx-family:mainfrom
Yradex:slice/element-template/08
May 14, 2026
Merged

feat(react): support element template events#2589
Yradex merged 6 commits into
lynx-family:mainfrom
Yradex:slice/element-template/08

Conversation

@Yradex
Copy link
Copy Markdown
Collaborator

@Yradex Yradex commented May 9, 2026

Summary by CodeRabbit

  • New Features

    • Experimental Element Template direct-event support: queued delivery, controlled flush on hydration, and reliable background↔main-thread event synchronization.
    • Improved attribute-slot preparation and normalization for more consistent hydration and native template creation.
  • Tests

    • Broad test coverage added for event registration/dispatch, queued delivery/flush semantics, hydration edge cases, compiled templates, and teardown behavior.
  • Chores

    • Marked as no package release required (internal/experimental).

Overview

This PR adds the Element Template event implementation across transform output, runtime attribute-slot handling, background hydration/update behavior, and native bridge wiring. It keeps the work behind the existing experimental Element Template path while making direct event attributes flow through the ET runtime instead of being treated as ordinary static attributes.

Key Points

  • Adds an ET attr-plan side table so transformed templates can identify event attribute slots without changing the serialized template shape for non-event attrs.
  • Prepares event attribute slots before main-thread create and background hydrate/update paths, including uid-based event values and handler registration.
  • Wires ET native event dispatch through the runtime bridge and covers direct-event subtree migration behavior.
  • Organizes event-focused runtime coverage and compiled fixtures, and includes an empty changeset because this remains unpublished experimental ET internals.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added (or not required).

Verification

  • git diff --check github/main..stack/element-template
  • pnpm --dir packages/react/runtime run test:et
  • cargo test --test element_template --test element_template_contract

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 9, 2026

🦋 Changeset detected

Latest commit: e759547

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds per-template attribute-slot plans, attribute adapters, reserved handle allocation, background raw-slot buffering and lazy preparation, manager lookup for event-values, queued runtime event dispatch with lifecycle controls, conditioned hydration flush, native wiring/teardown, and extensive tests/fixtures.

Changes

Element Template Event Attribute System

Layer / File(s) Summary
JSX transform emission & tests
packages/react/transform/crates/swc_plugin_element_template/*
Detects event dynamic attributes, imports internal runtime, and emits per-template __etAttrPlanMap assignments with adapter calls; adds/updates transform tests.
Attribute Slot Plan Types
packages/react/runtime/src/element-template/runtime/template/attr-slot-plan.ts, packages/react/runtime/src/element-template/internal.ts
Adds EtAttrAdapter/EtAttrPlan types, a stable __etAttrPlanMap registry, adaptEventAttrSlot(handleId, idx, value) to serialize event slots, and clearEtAttrPlanMap() for cleanup.
Render & reserved-handle creation
packages/react/runtime/src/element-template/runtime/render/render-opcodes.ts, packages/react/runtime/src/element-template/runtime/template/handle.ts
During __OpEnd, reads __etAttrPlanMap; when a plan exists reserves a template handle id, clones/adapts attributeSlots per plan, and creates the native template with the reserved handle.
Background prepareAttributeSlots
packages/react/runtime/src/element-template/background/attr-slots.ts
Implements prepareAttributeSlots(templateKey, handleId, rawSlots) that normalizes undefinednull and applies plan adapters to produce prepared SerializableValue[] for native creation.
Instance buffering & lazy native prep
packages/react/runtime/src/element-template/background/instance.ts
Buffers raw attributeSlots, adds prepareAttributeSlotsForNative() and getRawAttributeSlot(), prepares slots lazily, emits prepared slots on create, and updates setAttribute/tearDown behavior.
Manager lookup helper & tests
packages/react/runtime/src/element-template/background/manager.ts, packages/react/runtime/__test__/element-template/runtime/background/manager.test.ts
Adds getRawAttributeValueByEventValue(eventValue) to parse serialized event-values and return raw slot or spread-keyed properties; adds unit tests for invalid/spread cases.
Event prop adapters: runtime dispatch & queue
packages/react/runtime/src/element-template/prop-adapters/event.ts
Adds EtEventHandler type, centralized dispatch using manager lookup, publishEvent/publicComponentEvent, FIFO pendingEvents queue, lifecycle controls clearEventState/resetEventStateForRuntime, getEventHandlerForEventValue, and flushPendingEvents.
Native wiring & destroy
packages/react/runtime/src/element-template/native/index.ts, packages/react/runtime/src/element-template/background/destroy.ts
Calls resetEventStateForRuntime() during background init and assigns publishEvent/publicComponentEvent onto lynxCoreInject.tt; calls clearEventState() during background runtime destroy.
Hydration listener & prepare timing
packages/react/runtime/src/element-template/background/hydration-listener.ts, packages/react/runtime/src/element-template/background/hydrate.ts
Hydration listener conditionally flushes pending events based on whether hydrate emitted updates and whether dispatch succeeded; hydrate/emitCreateSubtree call prepareAttributeSlotsForNative() at additional points before native create.
Tests & fixtures
packages/react/runtime/__test__/element-template/**, packages/react/transform/**, packages/webpack/**, .changeset/*
Adds/updates unit and integration tests (prop-adapters, render-opcodes, hydration, instance, manager, compiled-fixtures), updates fixtures and outputs to use serialized event-values, adjusts native test wiring, and adds a changeset noting no release required.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • HuJean
  • upupming
  • hzy
  • colinaaa
  • luhc228
  • gaoachao

"🐰 I buffered slots and set them straight,
queued tiny hops to reach their fate,
transforms whispered plans in code,
hydration woke and flushes flowed,
native handlers clap — events celebrate! 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(react): support element template events' directly and clearly summarizes the main objective of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

❌ Patch coverage is 96.08541% with 11 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ackground/event/conditional-direct-event/index.tsx 0.00% 4 Missing ⚠️
...e/fixtures/background/event/direct-event/index.tsx 0.00% 4 Missing ⚠️
...ansform/crates/swc_plugin_element_template/napi.rs 0.00% 2 Missing ⚠️
...ransform/crates/swc_plugin_element_template/lib.rs 98.83% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Yradex Yradex force-pushed the slice/element-template/08 branch from 12a0d13 to c2dd2ad Compare May 9, 2026 07:41
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 9, 2026

Merging this PR will improve performance by 18.17%

⚡ 2 improved benchmarks
✅ 79 untouched benchmarks
⏩ 26 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
008-many-use-state-destroyBackground 9.5 ms 8 ms +19.29%
transform 1000 view elements 46.8 ms 40 ms +17.05%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing Yradex:slice/element-template/08 (e759547) with main (ae67fd1)

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 9, 2026

React Example

#8155 Bundle Size — 236.51KiB (0%).

e759547(current) vs ae67fd1 main#8152(baseline)

Bundle metrics  no changes
                 Current
#8155
     Baseline
#8152
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 197 197
No change  Duplicate Modules 80 80
No change  Duplicate Code 44.87% 44.87%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8155
     Baseline
#8152
No change  IMG 145.76KiB 145.76KiB
No change  Other 90.75KiB 90.75KiB

Bundle analysis reportBranch Yradex:slice/element-template/08Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 9, 2026

Web Explorer

#9730 Bundle Size — 901.38KiB (0%).

e759547(current) vs ae67fd1 main#9727(baseline)

Bundle metrics  no changes
                 Current
#9730
     Baseline
#9727
No change  Initial JS 45.06KiB 45.06KiB
No change  Initial CSS 2.22KiB 2.22KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 9 9
No change  Assets 11 11
No change  Modules 229 229
No change  Duplicate Modules 11 11
No change  Duplicate Code 27.22% 27.22%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9730
     Baseline
#9727
No change  JS 497.1KiB 497.1KiB
No change  Other 402.06KiB 402.06KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch Yradex:slice/element-template/08Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 9, 2026

React External

#1270 Bundle Size — 693.04KiB (0%).

e759547(current) vs ae67fd1 main#1267(baseline)

Bundle metrics  no changes
                 Current
#1270
     Baseline
#1267
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1270
     Baseline
#1267
No change  Other 693.04KiB 693.04KiB

Bundle analysis reportBranch Yradex:slice/element-template/08Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 9, 2026

React Example with Element Template

#422 Bundle Size — 199.83KiB (+1.03%).

e759547(current) vs ae67fd1 main#419(baseline)

Bundle metrics  Change 4 changes Regression 1 regression
                 Current
#422
     Baseline
#419
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 26.31% 0%
No change  Chunks 0 0
No change  Assets 4 4
Change  Modules 85(+6.25%) 80
Regression  Duplicate Modules 25(+8.7%) 23
Change  Duplicate Code 39.96%(-0.84%) 40.3%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#422
     Baseline
#419
No change  IMG 145.76KiB 145.76KiB
Regression  Other 54.08KiB (+3.93%) 52.03KiB

Bundle analysis reportBranch Yradex:slice/element-template/08Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 9, 2026

React MTF Example

#1288 Bundle Size — 207.46KiB (0%).

e759547(current) vs ae67fd1 main#1285(baseline)

Bundle metrics  no changes
                 Current
#1288
     Baseline
#1285
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 192 192
No change  Duplicate Modules 77 77
No change  Duplicate Code 44.38% 44.38%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1288
     Baseline
#1285
No change  IMG 111.23KiB 111.23KiB
No change  Other 96.23KiB 96.23KiB

Bundle analysis reportBranch Yradex:slice/element-template/08Project dashboard


Generated by RelativeCIDocumentationReport issue

@Yradex Yradex force-pushed the slice/element-template/08 branch from c2dd2ad to ffb9f66 Compare May 9, 2026 08:31
@Yradex Yradex marked this pull request as ready for review May 9, 2026 09:08
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffb9f66900

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/react/runtime/src/element-template/background/attr-slots.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react/transform/crates/swc_plugin_element_template/lib.rs (1)

159-210: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Line 161 breaks element-template in Development mode — the internal entry does not export required symbols.

The change on line 161 routes dev-mode runtime_id through internal_runtime_pkg(&cfg.runtime_pkg), which resolves to @lynx-js/react/element-template/internal. However, the element-template transformer generates code that calls transformRef on runtime_id (see lowering.rs line 62: $runtime_id.transformRef($value)). The element-template internal entry does not export transformRef — only the main @lynx-js/react/internal entry does. This causes runtime failures when dev builds execute.

Additionally, the dev-mode initializers for runtime_id and internal_runtime_id are now identical (both resolve via internal_runtime_pkg), creating unnecessary duplication.

Proposed fix
       runtime_id: match mode {
         TransformMode::Development => {
-          let runtime_pkg = internal_runtime_pkg(&cfg.runtime_pkg);
+          let runtime_pkg = cfg.runtime_pkg.clone();
           lazy_runtime_id(move || {
             Expr::Call(CallExpr {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/transform/crates/swc_plugin_element_template/lib.rs` around
lines 159 - 210, The dev-mode branch incorrectly uses internal_runtime_pkg for
runtime_id (making runtime_id point to the element-template internal entry which
lacks transformRef) and duplicates both initializers; change the Development arm
for runtime_id to use the public runtime package helper (use
runtime_pkg(&cfg.runtime_pkg) or the equivalent non-internal helper) so
runtime_id resolves to the main package that exports transformRef, while keeping
internal_runtime_id using internal_runtime_pkg(&cfg.runtime_pkg); ensure the
lazy_runtime_id closures differ (runtime_id -> require(main runtime pkg),
internal_runtime_id -> require(internal runtime pkg)).
🧹 Nitpick comments (2)
packages/react/transform/crates/swc_plugin_element_template/lib.rs (2)

159-210: ⚡ Quick win

Deduplicate the dev-mode Lazy constructions.

The two TransformMode::Development arms (lines 160–180 and 186–206) now build the same require(...) CallExpr against the same runtime_pkg. Even if the line-161 change is intentional, this is verbatim copy/paste; once they diverge for real reasons, they'll likely diverge by mistake. A small helper closure or function fn require_runtime_expr(pkg: String) -> RuntimeIdInitializer would collapse both arms to one line each.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/transform/crates/swc_plugin_element_template/lib.rs` around
lines 159 - 210, The two TransformMode::Development arms for runtime_id and
internal_runtime_id duplicate the same lazy require(...) CallExpr construction;
refactor by extracting a helper (e.g., fn require_runtime_expr(pkg: String) ->
RuntimeIdInitializer or a closure) that builds the Expr::Call(CallExpr) using
internal_runtime_pkg(&cfg.runtime_pkg) and reuse it in both lazy_runtime_id(...)
calls so runtime_id and internal_runtime_id each call the helper instead of
duplicating the require construction.

58-79: 💤 Low value

Simplify internal_runtime_pkg to check only /internal suffix.

Backslash and extension variants in INTERNAL_SUFFIXES are unreachable. Runtime module specifiers always use / regardless of OS, and no caller adds file extensions—all values are pure module specifiers like @lynx-js/react. Only the /internal suffix is ever used in practice.

Suggested simplification
 fn internal_runtime_pkg(runtime_pkg: &str) -> String {
-  const INTERNAL_SUFFIXES: &[&str] = &[
-    "/internal",
-    "/internal.ts",
-    "/internal.js",
-    "/internal.mjs",
-    "/internal.cjs",
-    "\\internal",
-    "\\internal.ts",
-    "\\internal.js",
-    "\\internal.mjs",
-    "\\internal.cjs",
-  ];
-  if INTERNAL_SUFFIXES
-    .iter()
-    .any(|suffix| runtime_pkg.ends_with(suffix))
-  {
+  if runtime_pkg.ends_with("/internal") {
     runtime_pkg.to_string()
   } else {
     format!("{runtime_pkg}/internal")
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/transform/crates/swc_plugin_element_template/lib.rs` around
lines 58 - 79, internal_runtime_pkg currently checks many unreachable suffix
variants (backslashes and extensions); simplify it to only test for the
"/internal" suffix. Replace the INTERNAL_SUFFIXES array and its .iter().any(...)
call in the internal_runtime_pkg function with a single ends_with("/internal")
check on runtime_pkg and return runtime_pkg.to_string() when true, otherwise
return format!("{runtime_pkg}/internal"); keep the function name
internal_runtime_pkg and behavior otherwise unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react/runtime/src/element-template/prop-adapters/event.ts`:
- Around line 56-58: The pendingEvents queue (used when queuePendingEvents is
true) is unbounded and can grow indefinitely; add a MAX_PENDING_EVENTS constant
and enforce it in the push path for pendingEvents (e.g., before or after pushing
the new [eventValue, data], drop the oldest entries using pendingEvents.shift()
until length <= MAX_PENDING_EVENTS) so the queue never exceeds the cap; update
any related logic in the same module that reads pendingEvents to expect this
bounded behavior.

---

Outside diff comments:
In `@packages/react/transform/crates/swc_plugin_element_template/lib.rs`:
- Around line 159-210: The dev-mode branch incorrectly uses internal_runtime_pkg
for runtime_id (making runtime_id point to the element-template internal entry
which lacks transformRef) and duplicates both initializers; change the
Development arm for runtime_id to use the public runtime package helper (use
runtime_pkg(&cfg.runtime_pkg) or the equivalent non-internal helper) so
runtime_id resolves to the main package that exports transformRef, while keeping
internal_runtime_id using internal_runtime_pkg(&cfg.runtime_pkg); ensure the
lazy_runtime_id closures differ (runtime_id -> require(main runtime pkg),
internal_runtime_id -> require(internal runtime pkg)).

---

Nitpick comments:
In `@packages/react/transform/crates/swc_plugin_element_template/lib.rs`:
- Around line 159-210: The two TransformMode::Development arms for runtime_id
and internal_runtime_id duplicate the same lazy require(...) CallExpr
construction; refactor by extracting a helper (e.g., fn
require_runtime_expr(pkg: String) -> RuntimeIdInitializer or a closure) that
builds the Expr::Call(CallExpr) using internal_runtime_pkg(&cfg.runtime_pkg) and
reuse it in both lazy_runtime_id(...) calls so runtime_id and
internal_runtime_id each call the helper instead of duplicating the require
construction.
- Around line 58-79: internal_runtime_pkg currently checks many unreachable
suffix variants (backslashes and extensions); simplify it to only test for the
"/internal" suffix. Replace the INTERNAL_SUFFIXES array and its .iter().any(...)
call in the internal_runtime_pkg function with a single ends_with("/internal")
check on runtime_pkg and return runtime_pkg.to_string() when true, otherwise
return format!("{runtime_pkg}/internal"); keep the function name
internal_runtime_pkg and behavior otherwise unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bf0a7efc-c77b-4f94-917b-dd00b7534d1d

📥 Commits

Reviewing files that changed from the base of the PR and between 87c1759 and ffb9f66.

⛔ Files ignored due to path filters (4)
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_events_js.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_events_lepus.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_interpolated_text_with_siblings_js.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_keep_code_and_template_attribute_slots_in_sync_for_spread.snap is excluded by !**/*.snap
📒 Files selected for processing (31)
  • .changeset/empty-et-event-implementation.md
  • packages/react/runtime/__test__/element-template/fixtures/background/event/conditional-direct-event/index.tsx
  • packages/react/runtime/__test__/element-template/fixtures/background/event/direct-event/index.tsx
  • packages/react/runtime/__test__/element-template/fixtures/render/react-example/index.js.txt
  • packages/react/runtime/__test__/element-template/fixtures/render/react-example/output.txt
  • packages/react/runtime/__test__/element-template/fixtures/render/react-example/papi.txt
  • packages/react/runtime/__test__/element-template/native/index.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/event/compiled-fixtures.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/background/hydrate.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/instance.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/update/compiled-fixtures.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/__test__/element-template/runtime/prop-adapters/event.test.ts
  • packages/react/runtime/__test__/element-template/runtime/render/render-opcodes-into-element-template.et.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/template/element-template-attr-slot-plan.test.ts
  • packages/react/runtime/src/element-template/background/attr-slots.ts
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/src/element-template/background/hydrate.ts
  • packages/react/runtime/src/element-template/background/hydration-listener.ts
  • packages/react/runtime/src/element-template/background/instance.ts
  • packages/react/runtime/src/element-template/index.ts
  • packages/react/runtime/src/element-template/internal.ts
  • packages/react/runtime/src/element-template/native/index.ts
  • packages/react/runtime/src/element-template/prop-adapters/event.ts
  • packages/react/runtime/src/element-template/runtime/render/render-opcodes.ts
  • packages/react/runtime/src/element-template/runtime/template/attr-slot-plan.ts
  • packages/react/runtime/src/element-template/runtime/template/handle.ts
  • packages/react/transform/crates/swc_plugin_element_template/lib.rs
  • packages/react/transform/crates/swc_plugin_element_template/napi.rs
  • packages/react/transform/crates/swc_plugin_element_template/tests/element_template_contract.rs
  • packages/webpack/react-webpack-plugin/test/background-loader.test.ts
💤 Files with no reviewable changes (1)
  • packages/react/runtime/src/element-template/index.ts

Comment thread packages/react/runtime/src/element-template/prop-adapters/event.ts
@Yradex Yradex force-pushed the slice/element-template/08 branch from ffb9f66 to a666c27 Compare May 9, 2026 09:42
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/react/transform/crates/swc_plugin_element_template/lowering.rs (2)

57-61: ⚡ Quick win

Remove redundant conditional branch for AttrName::Ref.

The AttrName::Ref case (line 57-58) now returns value directly, which is identical to the else branch (lines 59-60). Since the behavior is the same, the entire else if let AttrName::Ref check can be removed.

♻️ Proposed simplification
         } => {
           let slot_value = if let AttrName::Event = attr_name {
             if target == TransformTarget::LEPUS {
               quote!("1" as Expr)
             } else {
               value
             }
-          } else if let AttrName::Ref = attr_name {
-            value
           } else {
             value
           };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/transform/crates/swc_plugin_element_template/lowering.rs`
around lines 57 - 61, Remove the redundant conditional branch checking
AttrName::Ref that returns the same value as the final else: locate the block
containing "else if let AttrName::Ref" and the following "else" that both return
value, delete the entire "else if let AttrName::Ref { ... }" branch and leave
the existing else branch to return value; ensure references to AttrName::Ref are
not required elsewhere in that expression (adjust surrounding control flow if
needed) so only one branch returns value.

27-27: 💤 Low value

Consider removing unused _runtime_id parameter if not needed for API stability.

The runtime_id parameter was renamed to _runtime_id to indicate it's unused after removing the transformRef call. If this parameter is not required for maintaining a consistent API signature or future use, it could be removed entirely.

However, if other call sites or API contracts depend on this signature, keeping it is appropriate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/transform/crates/swc_plugin_element_template/lowering.rs` at
line 27, The _runtime_id field is unused; remove the `_runtime_id: Expr` field
declaration from the struct in lowering.rs and then update all related code
(constructors, struct initializers, pattern matches, and any places referencing
`runtime_id` or `_runtime_id`) to no longer pass or destructure that field; if
external API compatibility must be preserved instead, leave the field but ensure
it's consistently named `_runtime_id` and unused to avoid warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/react/transform/crates/swc_plugin_element_template/lowering.rs`:
- Around line 57-61: Remove the redundant conditional branch checking
AttrName::Ref that returns the same value as the final else: locate the block
containing "else if let AttrName::Ref" and the following "else" that both return
value, delete the entire "else if let AttrName::Ref { ... }" branch and leave
the existing else branch to return value; ensure references to AttrName::Ref are
not required elsewhere in that expression (adjust surrounding control flow if
needed) so only one branch returns value.
- Line 27: The _runtime_id field is unused; remove the `_runtime_id: Expr` field
declaration from the struct in lowering.rs and then update all related code
(constructors, struct initializers, pattern matches, and any places referencing
`runtime_id` or `_runtime_id`) to no longer pass or destructure that field; if
external API compatibility must be preserved instead, leave the field but ensure
it's consistently named `_runtime_id` and unused to avoid warnings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c6ebde11-20c9-40b4-87d5-d4eada8fad4c

📥 Commits

Reviewing files that changed from the base of the PR and between ffb9f66 and a666c27.

⛔ Files ignored due to path filters (6)
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_events_js.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_events_lepus.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_interpolated_text_with_siblings_js.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_refs_js.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_handle_refs_lepus.snap is excluded by !**/*.snap
  • packages/react/transform/crates/swc_plugin_element_template/tests/__combined_snapshots__/should_keep_code_and_template_attribute_slots_in_sync_for_spread.snap is excluded by !**/*.snap
📒 Files selected for processing (32)
  • .changeset/empty-et-event-implementation.md
  • packages/react/runtime/__test__/element-template/fixtures/background/event/conditional-direct-event/index.tsx
  • packages/react/runtime/__test__/element-template/fixtures/background/event/direct-event/index.tsx
  • packages/react/runtime/__test__/element-template/fixtures/render/react-example/index.js.txt
  • packages/react/runtime/__test__/element-template/fixtures/render/react-example/output.txt
  • packages/react/runtime/__test__/element-template/fixtures/render/react-example/papi.txt
  • packages/react/runtime/__test__/element-template/native/index.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/event/compiled-fixtures.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/background/hydrate.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/instance.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/update/compiled-fixtures.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/__test__/element-template/runtime/prop-adapters/event.test.ts
  • packages/react/runtime/__test__/element-template/runtime/render/render-opcodes-into-element-template.et.test.tsx
  • packages/react/runtime/src/element-template/background/attr-slots.ts
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/src/element-template/background/hydrate.ts
  • packages/react/runtime/src/element-template/background/hydration-listener.ts
  • packages/react/runtime/src/element-template/background/instance.ts
  • packages/react/runtime/src/element-template/index.ts
  • packages/react/runtime/src/element-template/internal.ts
  • packages/react/runtime/src/element-template/native/index.ts
  • packages/react/runtime/src/element-template/prop-adapters/event.ts
  • packages/react/runtime/src/element-template/runtime/render/render-opcodes.ts
  • packages/react/runtime/src/element-template/runtime/template/attr-slot-plan.ts
  • packages/react/runtime/src/element-template/runtime/template/handle.ts
  • packages/react/transform/crates/swc_plugin_element_template/lib.rs
  • packages/react/transform/crates/swc_plugin_element_template/lowering.rs
  • packages/react/transform/crates/swc_plugin_element_template/napi.rs
  • packages/react/transform/crates/swc_plugin_element_template/tests/element_template.rs
  • packages/react/transform/crates/swc_plugin_element_template/tests/element_template_contract.rs
  • packages/webpack/react-webpack-plugin/test/background-loader.test.ts
💤 Files with no reviewable changes (1)
  • packages/react/runtime/src/element-template/index.ts
✅ Files skipped from review due to trivial changes (8)
  • packages/react/transform/crates/swc_plugin_element_template/napi.rs
  • packages/react/runtime/test/element-template/fixtures/render/react-example/papi.txt
  • packages/webpack/react-webpack-plugin/test/background-loader.test.ts
  • packages/react/runtime/src/element-template/background/destroy.ts
  • .changeset/empty-et-event-implementation.md
  • packages/react/transform/crates/swc_plugin_element_template/tests/element_template.rs
  • packages/react/runtime/test/element-template/fixtures/render/react-example/output.txt
  • packages/react/runtime/test/element-template/fixtures/background/event/conditional-direct-event/index.tsx
🚧 Files skipped from review as they are similar to previous changes (20)
  • packages/react/runtime/src/element-template/native/index.ts
  • packages/react/runtime/src/element-template/internal.ts
  • packages/react/runtime/test/element-template/runtime/background/update/compiled-fixtures.test.tsx
  • packages/react/runtime/src/element-template/background/hydration-listener.ts
  • packages/react/runtime/src/element-template/background/attr-slots.ts
  • packages/react/runtime/test/element-template/runtime/prop-adapters/event.test.ts
  • packages/react/runtime/src/element-template/runtime/render/render-opcodes.ts
  • packages/react/runtime/src/element-template/runtime/template/handle.ts
  • packages/react/runtime/test/element-template/native/index.test.ts
  • packages/react/runtime/src/element-template/background/hydrate.ts
  • packages/react/runtime/test/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/test/element-template/runtime/background/event/compiled-fixtures.test.tsx
  • packages/react/runtime/src/element-template/runtime/template/attr-slot-plan.ts
  • packages/react/transform/crates/swc_plugin_element_template/lib.rs
  • packages/react/runtime/test/element-template/fixtures/background/event/direct-event/index.tsx
  • packages/react/runtime/test/element-template/runtime/render/render-opcodes-into-element-template.et.test.tsx
  • packages/react/transform/crates/swc_plugin_element_template/tests/element_template_contract.rs
  • packages/react/runtime/test/element-template/runtime/background/hydrate.test.ts
  • packages/react/runtime/src/element-template/prop-adapters/event.ts
  • packages/react/runtime/src/element-template/background/instance.ts

@Yradex Yradex force-pushed the slice/element-template/08 branch from a666c27 to 1da2f26 Compare May 12, 2026 11:27
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/react/runtime/src/element-template/background/attr-slots.ts (1)

32-44: 💤 Low value

Main logic is correct; consider optional defensive checks.

The attribute plan iteration and adapter application logic is sound. The double normalization and cloning at lines 32-35 is necessary to allow safe mutation when attrPlan exists.

Optionally, for robustness in case of malformed plans (lines 37-39), you could add bounds/type validation:

  • Validate attrSlotIndex is within rawSlots.length
  • Validate adapter is callable

Given the experimental ET flag and internal usage where plans are transform-generated, these checks are not critical but would make the code more defensive.

♻️ Optional: add defensive validation
   for (let planIndex = 0; planIndex < attrPlan.length; planIndex += 2) {
     const attrSlotIndex = attrPlan[planIndex] as number;
     const adapter = attrPlan[planIndex + 1] as EtAttrAdapter;
+    // Optional defensive checks:
+    // if (attrSlotIndex < 0 || attrSlotIndex >= rawSlots.length) {
+    //   throw new RangeError(`Invalid attr slot index: ${attrSlotIndex}`);
+    // }
+    // if (typeof adapter !== 'function') {
+    //   throw new TypeError(`Invalid adapter at plan index ${planIndex + 1}`);
+    // }
     const rawValue = rawSlots[attrSlotIndex];
     preparedSlots[attrSlotIndex] = adapter(handleId, attrSlotIndex, rawValue);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/runtime/src/element-template/background/attr-slots.ts` around
lines 32 - 44, Add defensive validation inside the attrPlan iteration: before
calling adapter(handleId, attrSlotIndex, rawValue) in the loop that references
attrPlan, check that attrSlotIndex is a finite integer within [0,
rawSlots.length) and that adapter is a callable (function) matching
EtAttrAdapter; if either check fails, skip that plan entry (leave
preparedSlots[attrSlotIndex] unchanged) or optionally continue to next entry.
Place these checks around the existing adapter invocation in the same loop so
malformed plans won't throw when calling adapter or indexing rawSlots.
packages/react/runtime/src/element-template/prop-adapters/event.ts (2)

50-56: 💤 Low value

Clarify the unused _componentId parameter.

The _componentId parameter is prefixed with underscore but never used. If this parameter is required for API compatibility or future extension, consider adding a brief comment explaining why it's present.

📝 Suggested clarification
+// componentId parameter reserved for future component-scoped event routing
 export function publicComponentEvent(
   _componentId: string,
   eventValue: string,
   data: unknown,
 ): void {
   publishEvent(eventValue, data);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/runtime/src/element-template/prop-adapters/event.ts` around
lines 50 - 56, The parameter _componentId in the publicComponentEvent function
is unused which is confusing; add a brief inline comment next to the
_componentId parameter (or above the function) stating that it is intentionally
unused for API compatibility/future use, referencing the function name
publicComponentEvent and the publishEvent call so reviewers know this was
considered and left intentionally blank.

58-67: Event re-entrancy behavior during flush.

Line 59 disables queuing before processing pending events. If an event handler invoked during the flush loop calls publishEvent, that new event will either dispatch immediately or be silently dropped (depending on whether a handler exists). This prevents infinite loops but could lead to lost events if handlers trigger cascading events. This appears intentional, but worth noting if the behavior is not documented elsewhere.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/runtime/src/element-template/prop-adapters/event.ts` around
lines 58 - 67, flushPendingEvents currently disables queuing (queuePendingEvents
= false) before iterating, so any publishEvent called from a handler may be
dropped or dispatched immediately; change the flush logic so re-entrant
publishEvent calls are enqueued and not lost: inside flushPendingEvents, process
pendingEvents in a loop that continues until pendingEvents is empty, re-enabling
queuing (set queuePendingEvents = true) while dispatching handlers so
publishEvent will push new events onto pendingEvents, then after draining the
current batch set queuePendingEvents = false and repeat until pendingEvents is
empty; reference function flushPendingEvents and variables queuePendingEvents,
pendingEvents, publishEvent, and dispatchEvent when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react/runtime/src/element-template/background/attr-slots.ts`:
- Around line 9-20: The function normalizeAttributeSlots currently casts
rawSlots to SerializableValue[] without validating entries; add a runtime type
guard inside normalizeAttributeSlots to verify each non-undefined rawSlot is a
valid SerializableValue (using an existing isSerializableValue helper if
available, or a small runtime check) before returning or mutating
normalizedSlots, and if any entry fails validation either coerce/replace it or
throw/log a clear error indicating the offending slot index; update references
to rawSlots, rawSlot, and normalizedSlots in the function to use the guard so
the final return is safe without an unchecked "as SerializableValue[]" cast.

---

Nitpick comments:
In `@packages/react/runtime/src/element-template/background/attr-slots.ts`:
- Around line 32-44: Add defensive validation inside the attrPlan iteration:
before calling adapter(handleId, attrSlotIndex, rawValue) in the loop that
references attrPlan, check that attrSlotIndex is a finite integer within [0,
rawSlots.length) and that adapter is a callable (function) matching
EtAttrAdapter; if either check fails, skip that plan entry (leave
preparedSlots[attrSlotIndex] unchanged) or optionally continue to next entry.
Place these checks around the existing adapter invocation in the same loop so
malformed plans won't throw when calling adapter or indexing rawSlots.

In `@packages/react/runtime/src/element-template/prop-adapters/event.ts`:
- Around line 50-56: The parameter _componentId in the publicComponentEvent
function is unused which is confusing; add a brief inline comment next to the
_componentId parameter (or above the function) stating that it is intentionally
unused for API compatibility/future use, referencing the function name
publicComponentEvent and the publishEvent call so reviewers know this was
considered and left intentionally blank.
- Around line 58-67: flushPendingEvents currently disables queuing
(queuePendingEvents = false) before iterating, so any publishEvent called from a
handler may be dropped or dispatched immediately; change the flush logic so
re-entrant publishEvent calls are enqueued and not lost: inside
flushPendingEvents, process pendingEvents in a loop that continues until
pendingEvents is empty, re-enabling queuing (set queuePendingEvents = true)
while dispatching handlers so publishEvent will push new events onto
pendingEvents, then after draining the current batch set queuePendingEvents =
false and repeat until pendingEvents is empty; reference function
flushPendingEvents and variables queuePendingEvents, pendingEvents,
publishEvent, and dispatchEvent when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 388735b8-2d7e-446d-9c45-394d408957e1

📥 Commits

Reviewing files that changed from the base of the PR and between a666c27 and 1da2f26.

📒 Files selected for processing (14)
  • .changeset/empty-et-event-implementation.md
  • packages/react/runtime/__test__/element-template/native/index.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/event/compiled-fixtures.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/background/hydrate.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/instance.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/update/compiled-fixtures.test.tsx
  • packages/react/runtime/__test__/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/__test__/element-template/runtime/prop-adapters/event.test.ts
  • packages/react/runtime/src/element-template/background/attr-slots.ts
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/src/element-template/background/instance.ts
  • packages/react/runtime/src/element-template/background/manager.ts
  • packages/react/runtime/src/element-template/native/index.ts
  • packages/react/runtime/src/element-template/prop-adapters/event.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/empty-et-event-implementation.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/test/element-template/native/index.test.ts
  • packages/react/runtime/test/element-template/runtime/background/hydrate.test.ts
  • packages/react/runtime/test/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/test/element-template/runtime/background/update/compiled-fixtures.test.tsx
  • packages/react/runtime/test/element-template/runtime/background/event/compiled-fixtures.test.tsx
  • packages/react/runtime/src/element-template/native/index.ts
  • packages/react/runtime/test/element-template/runtime/prop-adapters/event.test.ts
  • packages/react/runtime/src/element-template/background/instance.ts
  • packages/react/runtime/test/element-template/runtime/background/instance.test.ts

Comment thread packages/react/runtime/src/element-template/background/manager.ts
@Yradex Yradex force-pushed the slice/element-template/08 branch 2 times, most recently from 2049e2d to 2e32d7c Compare May 12, 2026 12:49
Comment thread packages/react/runtime/src/element-template/background/instance.ts
@Yradex Yradex force-pushed the slice/element-template/08 branch from 2e32d7c to e759547 Compare May 13, 2026 10:01
@Yradex Yradex merged commit e427f43 into lynx-family:main May 14, 2026
52 of 53 checks passed
@Yradex Yradex deleted the slice/element-template/08 branch May 14, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants