Skip to content

Allow JS root components to reinitialize on circuit restart#32

Open
tomerqodo wants to merge 2 commits intocoderabbit_full_base_allow_js_root_components_to_reinitialize_on_circuit_restart_pr7from
coderabbit_full_head_allow_js_root_components_to_reinitialize_on_circuit_restart_pr7
Open

Allow JS root components to reinitialize on circuit restart#32
tomerqodo wants to merge 2 commits intocoderabbit_full_base_allow_js_root_components_to_reinitialize_on_circuit_restart_pr7from
coderabbit_full_head_allow_js_root_components_to_reinitialize_on_circuit_restart_pr7

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 28, 2026

Benchmark PR from agentic-review-benchmarks#7

Summary by CodeRabbit

  • New Features

    • Enhanced dynamic JavaScript root component support with improved multi-renderer tracking and state management.
  • Bug Fixes

    • Resolved persistent state handling in dynamic JavaScript root components across different renderer contexts.
  • Tests

    • Consolidated and reorganized JavaScript root component tests for improved coverage and reliability.
    • Removed feature flag requirement for dynamic JS root component registration, now always enabled.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Walkthrough

The changes implement per-renderer tracking for dynamic JavaScript root components by adding rendererId parameter to the enableJSRootComponents function. This prevents cross-renderer conflicts while allowing same-renderer re-enablement. A test file is consolidated, and a runtime feature flag for JS root component registration is removed.

Changes

Cohort / File(s) Summary
Per-Renderer Tracking Implementation
src/Components/Web.JS/src/Rendering/JSRootComponents.ts
Adds currentRendererId and hasInitializedJsComponents state tracking. enableJSRootComponents now accepts rendererId parameter with guards: throws error on cross-renderer conflict, updates renderer on re-enablement without disposal, invokes initializers only once per session. Restructures initialization flow.
Call Site Update
src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts
Updates enableJSRootComponents call to pass rendererId as first argument, providing renderer context to enable logic.
Test Suite Reorganization
src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs
Deleted dedicated test file for persistent state in dynamic JS roots.
Test Suite Consolidation
src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
Adds new test method PersistentStateIsSupportedInDynamicJSRoots(string renderMode) with Theory cases for ServerNonPrerendered and WebAssemblyNonPrerendered render modes, navigating to dynamic-js-root.html and verifying state persistence across button clicks.
Test Server Configuration
src/Components/test/testassets/Components.TestServer/Program.cs, src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
Removes dictionary entry for "Razor Component Endpoints with JS Root Component" configuration option. Removes runtime feature flag gate, now unconditionally registering ComponentWithPersistentState as "dynamic-js-root-counter".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through renderers with care,
Tracking IDs in the digital air,
One component per frame, no overlap today,
Tests consolidated—the old ways give way!
Dynamic roots grow strong, flags fade to dust. 🌱

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete and largely off-topic, providing only a benchmark reference without following the required template structure or detailing the actual changes. Add a proper description following the template: include a summary of changes (under 80 chars), detailed description section, and reference any issue numbers in the format 'Fixes #{number}'.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: enabling JS root components to reinitialize when circuits restart.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@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

🤖 Fix all issues with AI agents
In `@src/Components/Web.JS/src/Rendering/JSRootComponents.ts`:
- Around line 126-130: The guard currently throws when manager exists and
currentRendererId === rendererId, which is inverted; update the condition in the
block that references manager, currentRendererId and rendererId so it throws
only when a different renderer is attempting to enable JS root components (i.e.,
change the check to manager && currentRendererId !== rendererId), and update the
inline comment if needed to reflect the correct cross-renderer protection logic
in JSRootComponents.ts.
- Around line 136-139: jsComponentParametersByIdentifier is no longer
initialized, causing RootComponentsFunctions.add to dereference undefined;
inside the same initialization block where currentRendererId and manager are set
(the if (!hasInitializedJsComponents) branch), initialize
jsComponentParametersByIdentifier to the same empty collection type it used to
be (e.g., {} or new Map depending on original usage), then set
hasInitializedJsComponents = true so subsequent calls like
RootComponentsFunctions.add can safely read/write it.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 505a364 and 7bb6a08.

📒 Files selected for processing (6)
  • src/Components/Web.JS/src/Rendering/JSRootComponents.ts
  • src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts
  • src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs
  • src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
  • src/Components/test/testassets/Components.TestServer/Program.cs
  • src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
💤 Files with no reviewable changes (2)
  • src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs
  • src/Components/test/testassets/Components.TestServer/Program.cs
🧰 Additional context used
🧬 Code graph analysis (2)
src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts (1)
src/Components/Web.JS/src/Rendering/JSRootComponents.ts (1)
  • enableJSRootComponents (120-151)
src/Components/Web.JS/src/Rendering/JSRootComponents.ts (1)
src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts (1)
  • DotNetObject (697-718)
🔇 Additional comments (4)
src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs (1)

54-55: Unconditional JS root registration looks fine here.

src/Components/test/E2ETest/Tests/StatePersistenceTest.cs (1)

279-291: Nice coverage for dynamic JS root persistence across render modes.

src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts (1)

32-35: Passing rendererId into enableJSRootComponents is the right update.

src/Components/Web.JS/src/Rendering/JSRootComponents.ts (1)

12-15: State tracking additions are reasonable for per-renderer lifecycle control.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +126 to 130
if (manager && currentRendererId === rendererId) {
// A different renderer type (e.g., Server vs WebAssembly) is trying to enable JS root components.
// This is a multi-host scenario which is not supported for dynamic root components.
throw new Error('Dynamic root components have already been enabled.');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard condition is inverted; it throws on same renderer instead of different renderer.
This defeats the cross-renderer protection and may block same-renderer re-enablement depending on rendererId stability.

🔧 Suggested fix
-  if (manager && currentRendererId === rendererId) {
+  if (manager && currentRendererId !== rendererId) {
     // A different renderer type (e.g., Server vs WebAssembly) is trying to enable JS root components.
     // This is a multi-host scenario which is not supported for dynamic root components.
     throw new Error('Dynamic root components have already been enabled.');
   }
🤖 Prompt for AI Agents
In `@src/Components/Web.JS/src/Rendering/JSRootComponents.ts` around lines 126 -
130, The guard currently throws when manager exists and currentRendererId ===
rendererId, which is inverted; update the condition in the block that references
manager, currentRendererId and rendererId so it throws only when a different
renderer is attempting to enable JS root components (i.e., change the check to
manager && currentRendererId !== rendererId), and update the inline comment if
needed to reflect the correct cross-renderer protection logic in
JSRootComponents.ts.

Comment on lines +136 to +139
currentRendererId = rendererId;
manager = managerInstance;
jsComponentParametersByIdentifier = jsComponentParameters;

// Call the registered initializers. This is an arbitrary subset of the JS component types that are registered
// on the .NET side - just those of them that require some JS-side initialization (e.g., to register them
// as custom elements).
for (const [initializerIdentifier, componentIdentifiers] of Object.entries(jsComponentInitializers)) {
const initializerFunc = DotNet.findJSFunction(initializerIdentifier, 0) as JSComponentInitializerCallback;
for (const componentIdentifier of componentIdentifiers) {
const parameters = jsComponentParameters[componentIdentifier];
initializerFunc(componentIdentifier, parameters);

if (!hasInitializedJsComponents) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

jsComponentParametersByIdentifier is never set after the change.
RootComponentsFunctions.add dereferences it, so this will throw at runtime once a dynamic root is added.

🔧 Suggested fix
   currentRendererId = rendererId;
   manager = managerInstance;
+  jsComponentParametersByIdentifier = jsComponentParameters;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
currentRendererId = rendererId;
manager = managerInstance;
jsComponentParametersByIdentifier = jsComponentParameters;
// Call the registered initializers. This is an arbitrary subset of the JS component types that are registered
// on the .NET side - just those of them that require some JS-side initialization (e.g., to register them
// as custom elements).
for (const [initializerIdentifier, componentIdentifiers] of Object.entries(jsComponentInitializers)) {
const initializerFunc = DotNet.findJSFunction(initializerIdentifier, 0) as JSComponentInitializerCallback;
for (const componentIdentifier of componentIdentifiers) {
const parameters = jsComponentParameters[componentIdentifier];
initializerFunc(componentIdentifier, parameters);
if (!hasInitializedJsComponents) {
currentRendererId = rendererId;
manager = managerInstance;
jsComponentParametersByIdentifier = jsComponentParameters;
if (!hasInitializedJsComponents) {
🤖 Prompt for AI Agents
In `@src/Components/Web.JS/src/Rendering/JSRootComponents.ts` around lines 136 -
139, jsComponentParametersByIdentifier is no longer initialized, causing
RootComponentsFunctions.add to dereference undefined; inside the same
initialization block where currentRendererId and manager are set (the if
(!hasInitializedJsComponents) branch), initialize
jsComponentParametersByIdentifier to the same empty collection type it used to
be (e.g., {} or new Map depending on original usage), then set
hasInitializedJsComponents = true so subsequent calls like
RootComponentsFunctions.add can safely read/write it.

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.

2 participants

Comments