Skip to content

feat(react): clean element template state on destroy#2579

Merged
Yradex merged 1 commit into
lynx-family:mainfrom
Yradex:slice/element-template/06
May 9, 2026
Merged

feat(react): clean element template state on destroy#2579
Yradex merged 1 commit into
lynx-family:mainfrom
Yradex:slice/element-template/06

Conversation

@Yradex
Copy link
Copy Markdown
Collaborator

@Yradex Yradex commented May 8, 2026

Summary by CodeRabbit

  • Tests

    • Enhanced test coverage for Element Template runtime lifecycle and cleanup behavior
    • Improved test isolation with explicit cleanup in test hooks
    • Added verification for error handling in cleanup sequences
  • Chores

    • Refactored Element Template runtime destruction to improve reliability
    • Enhanced cleanup mechanisms for removed subtree management
    • Better coordination of runtime state reset during lifecycle events

Overview

This PR makes Element Template destroy cleanup release the runtime-owned state on both background and main-thread paths.

Before this change, destroy paths only reset listeners. That left two ET-owned retention points outside the normal removed-subtree cleanup flow: delayed background cleanup timers could still run after runtime teardown, and the main-thread ElementTemplate registry could keep strong references after the page lifetime ended.

Key Points

  • Adds a background destroy entry point that removes the hydrate listener, resets commit state, cancels delayed removed-subtree cleanup timers, and clears the background instance manager.
  • Routes the BTS destroy lifetime hook through that background destroy entry point so teardown handles both listener state and retained ET background instances.
  • Adds a main-thread destroy helper that clears the ElementTemplate registry even if patch-listener reset throws, while preserving the original reset error for callers.
  • Includes an empty changeset because this only affects the unpublished Element Template runtime path and internal tests, without changing public APIs or release-facing defaults.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 8, 2026

🦋 Changeset detected

Latest commit: 2c76078

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 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 801a4723-8670-4ea2-91e0-bf6aaf2c1e78

📥 Commits

Reviewing files that changed from the base of the PR and between 1950cda and 2c76078.

📒 Files selected for processing (10)
  • .changeset/empty-et-destroy-cleanup.md
  • packages/react/runtime/__test__/element-template/native/callDestroyLifetimeFun.test.ts
  • packages/react/runtime/__test__/element-template/native/mts-destroy.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/commit-hook.test.ts
  • packages/react/runtime/__test__/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/src/element-template/background/commit-hook.ts
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/src/element-template/background/hydration-listener.ts
  • packages/react/runtime/src/element-template/native/callDestroyLifetimeFun.ts
  • packages/react/runtime/src/element-template/native/mts-destroy.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/empty-et-destroy-cleanup.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/test/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/test/element-template/runtime/background/commit-hook.test.ts
  • packages/react/runtime/test/element-template/native/callDestroyLifetimeFun.test.ts
  • packages/react/runtime/src/element-template/native/callDestroyLifetimeFun.ts
  • packages/react/runtime/src/element-template/background/hydration-listener.ts
  • packages/react/runtime/test/element-template/native/mts-destroy.test.ts
  • packages/react/runtime/src/element-template/background/commit-hook.ts
  • packages/react/runtime/src/element-template/native/mts-destroy.ts

📝 Walkthrough

Walkthrough

The PR refactors element-template runtime destruction by introducing explicit background and main-thread destroy functions with deterministic cleanup ordering, adding timer-based tracking for delayed removed-subtree teardown, decoupling hydration-listener reset from commit-state clearing, and updating call sites and tests to verify the new cleanup behavior.

Changes

Element Template Destruction Refactor

Layer / File(s) Summary
Delayed Teardown Timer Management
packages/react/runtime/src/element-template/background/commit-hook.ts
Module-level Set tracks active setTimeout handles for removed-subtree cleanup; scheduleElementTemplateRemovedSubtreeCleanup now registers/deregisters timers and new cancelElementTemplateRemovedSubtreeCleanup() clears all pending teardowns.
Hydration Listener and Commit State Separation
packages/react/runtime/src/element-template/background/hydration-listener.ts
installElementTemplateHydrationListener() now calls resetElementTemplateCommitState() after clearing prior listener; resetElementTemplateHydrationListener() no longer resets commit state, only removes the event listener.
Background Runtime Destruction
packages/react/runtime/src/element-template/background/destroy.ts
New destroyElementTemplateBackgroundRuntime() orchestrates: reset hydration listener, reset commit state, cancel delayed cleanup timers, and clear background instance manager.
Main-Thread Runtime Destruction
packages/react/runtime/src/element-template/native/mts-destroy.ts
New destroyElementTemplateMainThreadRuntime() attempts patch-listener reset with error capture, always clears ElementTemplateRegistry, rethrows error if reset failed; onMtsDestruction() now delegates to this function and uses finally for listener removal.
Native Call Site Updates
packages/react/runtime/src/element-template/native/callDestroyLifetimeFun.ts
callDestroyLifetimeFun() now calls destroyElementTemplateBackgroundRuntime() instead of directly resetting the hydration listener.
Background Cleanup Tests
packages/react/runtime/__test__/element-template/native/callDestroyLifetimeFun.test.ts, packages/react/runtime/__test__/element-template/runtime/background/commit-hook.test.ts
Expanded test suite verifies commit-context and instance-manager clearing after destroy, removed-subtree scheduling behavior, and hydration event handling with real runtime modules instead of mocks.
Main-Thread Cleanup Tests
packages/react/runtime/__test__/element-template/native/mts-destroy.test.ts
Added tests for registry clearing, error-safe registry clearing when patch-listener reset throws, improved test isolation with afterEach cleanup, and try/finally guards in native-bridge restoration.
Integration Test Updates
packages/react/runtime/__test__/element-template/runtime/hydration/hydration-listener.test.ts
Updated callDestroyLifetimeFun cleanup test assertion to expect background instance removal after hydration.
Changelog
.changeset/empty-et-destroy-cleanup.md
Declares no package release needed; only internal Element Template runtime and tests updated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • lynx-family/lynx-stack#2569: Both PRs refactor element-template removal/removed-subtree cleanup and registry teardown with background instance removal and main-thread registry clearing.
  • lynx-family/lynx-stack#2568: Both PRs modify element-template background subtree cleanup flow via background/commit-hook and hydration-listener modules with removed-subtree scheduling function changes.
  • lynx-family/lynx-stack#2576: Both PRs touch element-template removed-subtree handling and background-instance teardown with explicit destroy functions and cleanup refactoring.

Suggested reviewers

  • HuJean
  • hzy

Poem

🐰 A cleanup sequence, tidy and true,
Background destroyed, commit state too,
Timers cancelled before they could ring,
Registry cleared—oh what joy this brings!
No leaks in the lifecycle, all is now fine.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title accurately summarizes the main change: adding cleanup of element template state when destroying the runtime on both background and main-thread paths.
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

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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.

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.

Caution

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

⚠️ Outside diff range comments (1)
packages/react/runtime/__test__/element-template/native/mts-destroy.test.ts (1)

52-68: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore g.lynx.getNative in the performance-hooks test to avoid cross-test leakage.

This test mutates g.lynx.getNative but only restores g.lynx.performance. That can pollute later tests and create order-dependent failures.

Suggested fix
   it('does not throw when performance hooks are missing', () => {
     const g = globalThis as typeof globalThis & {
       lynx: typeof lynx & {
         performance: Partial<typeof lynx.performance>;
       };
     };
+    const originalGetNative = g.lynx.getNative;
     const originalPerformance = g.lynx.performance;
     const removeEventListener = vi.fn();
 
-    g.lynx.getNative = () => ({ addEventListener: vi.fn(), removeEventListener });
-    g.lynx.performance = {};
-
-    expect(() => onMtsDestruction()).not.toThrow();
-    expect(removeEventListener).toHaveBeenCalledWith('__DestroyLifetime', onMtsDestruction);
-
-    g.lynx.performance = originalPerformance;
+    try {
+      g.lynx.getNative = () => ({ addEventListener: vi.fn(), removeEventListener });
+      g.lynx.performance = {};
+
+      expect(() => onMtsDestruction()).not.toThrow();
+      expect(removeEventListener).toHaveBeenCalledWith('__DestroyLifetime', onMtsDestruction);
+    } finally {
+      g.lynx.getNative = originalGetNative;
+      g.lynx.performance = originalPerformance;
+    }
   });
🤖 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/__test__/element-template/native/mts-destroy.test.ts`
around lines 52 - 68, The test mutates g.lynx.getNative but never restores it,
risking cross-test leakage; capture the original g.lynx.getNative before you
overwrite it (e.g., store in a variable like originalGetNative), then restore
g.lynx.getNative = originalGetNative at the end of the test (after the expect
assertions). Keep references to the existing symbols onMtsDestruction and
g.lynx.performance as-is and only add saving/restoring of g.lynx.getNative to
prevent order-dependent failures.
🤖 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.

Outside diff comments:
In `@packages/react/runtime/__test__/element-template/native/mts-destroy.test.ts`:
- Around line 52-68: The test mutates g.lynx.getNative but never restores it,
risking cross-test leakage; capture the original g.lynx.getNative before you
overwrite it (e.g., store in a variable like originalGetNative), then restore
g.lynx.getNative = originalGetNative at the end of the test (after the expect
assertions). Keep references to the existing symbols onMtsDestruction and
g.lynx.performance as-is and only add saving/restoring of g.lynx.getNative to
prevent order-dependent failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4aefdfd3-d056-4a54-9a7d-68d583687d40

📥 Commits

Reviewing files that changed from the base of the PR and between 736ba38 and 1950cda.

📒 Files selected for processing (10)
  • .changeset/empty-et-destroy-cleanup.md
  • packages/react/runtime/__test__/element-template/native/callDestroyLifetimeFun.test.ts
  • packages/react/runtime/__test__/element-template/native/mts-destroy.test.ts
  • packages/react/runtime/__test__/element-template/runtime/background/commit-hook.test.ts
  • packages/react/runtime/__test__/element-template/runtime/hydration/hydration-listener.test.ts
  • packages/react/runtime/src/element-template/background/commit-hook.ts
  • packages/react/runtime/src/element-template/background/destroy.ts
  • packages/react/runtime/src/element-template/background/hydration-listener.ts
  • packages/react/runtime/src/element-template/native/callDestroyLifetimeFun.ts
  • packages/react/runtime/src/element-template/native/mts-destroy.ts

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 8, 2026

Merging this PR will degrade performance by 16.22%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 79 untouched benchmarks
⏩ 26 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
002-hello-reactLynx-destroyBackground 912.4 µs 863.4 µs +5.67%
008-many-use-state-destroyBackground 8 ms 9.5 ms -16.22%

Comparing Yradex:slice/element-template/06 (2c76078) with main (dcd0b98)2

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.

  2. No successful run was found on main (736ba38) during the generation of this report, so dcd0b98 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Yradex Yradex force-pushed the slice/element-template/06 branch from 1950cda to 2c76078 Compare May 8, 2026 09:53
@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 8, 2026

React Example

#7915 Bundle Size — 235.77KiB (0%).

2c76078(current) vs dcd0b98 main#7895(baseline)

Bundle metrics  no changes
                 Current
#7915
     Baseline
#7895
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.85% 44.85%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7915
     Baseline
#7895
No change  IMG 145.76KiB 145.76KiB
No change  Other 90.01KiB 90.01KiB

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


Generated by RelativeCIDocumentationReport issue

@Yradex Yradex marked this pull request as ready for review May 8, 2026 09:54
@Yradex Yradex requested review from HuJean and hzy as code owners May 8, 2026 09:54
@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 8, 2026

React MTF Example

#1045 Bundle Size — 206.69KiB (0%).

2c76078(current) vs dcd0b98 main#1025(baseline)

Bundle metrics  no changes
                 Current
#1045
     Baseline
#1025
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.36% 44.36%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1045
     Baseline
#1025
No change  IMG 111.23KiB 111.23KiB
No change  Other 95.46KiB 95.46KiB

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


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 8, 2026

Web Explorer

#9487 Bundle Size — 900.02KiB (0%).

2c76078(current) vs dcd0b98 main#9467(baseline)

Bundle metrics  no changes
                 Current
#9487
     Baseline
#9467
No change  Initial JS 44.46KiB 44.46KiB
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.28% 27.28%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9487
     Baseline
#9467
No change  JS 495.88KiB 495.88KiB
No change  Other 401.92KiB 401.92KiB
No change  CSS 2.22KiB 2.22KiB

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


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 8, 2026

React External

#1030 Bundle Size — 690.27KiB (0%).

2c76078(current) vs dcd0b98 main#1010(baseline)

Bundle metrics  no changes
                 Current
#1030
     Baseline
#1010
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
#1030
     Baseline
#1010
No change  Other 690.27KiB 690.27KiB

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


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 8, 2026

React Example with Element Template

#180 Bundle Size — 198.02KiB (-0.41%).

2c76078(current) vs dcd0b98 main#160(baseline)

Bundle metrics  Change 3 changes
                 Current
#180
     Baseline
#160
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 26.7% 0%
No change  Chunks 0 0
No change  Assets 4 4
Change  Modules 81(+3.85%) 78
No change  Duplicate Modules 23 23
Change  Duplicate Code 40.27%(-0.15%) 40.33%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Improvement 1 improvement
                 Current
#180
     Baseline
#160
No change  IMG 145.76KiB 145.76KiB
Improvement  Other 52.26KiB (-1.54%) 53.08KiB

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


Generated by RelativeCIDocumentationReport issue

@Yradex Yradex merged commit 60a345b into lynx-family:main May 9, 2026
81 of 85 checks passed
@Yradex Yradex deleted the slice/element-template/06 branch May 9, 2026 03:54
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.

2 participants