Skip to content

Conversation

AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Sep 18, 2025

Summary

Restrict usage of Pro features to React on Rails Pro license

This PR implements a comprehensive licensing structure that separates core MIT-licensed functionality from React on Rails Pro-licensed features. The changes establish clear boundaries between open source and proprietary code while maintaining backward compatibility.

🔐 License Structure Changes:

  • Moved Pro features into dedicated lib/react_on_rails/pro/ and node_package/src/pro/ directories
  • Added Pro license headers to all proprietary files
  • Updated LICENSE.md to clearly distinguish core MIT license from Pro-licensed directories
  • Implemented runtime Pro license validation and feature gating

⚡ Pro Feature Gating:

  • Immediate hydration functionality now requires Pro license validation
  • Pro warning badges displayed when Pro features are used without license
  • Graceful fallback to standard hydration when Pro license unavailable
  • Enhanced render options processing with explicit Pro feature detection

🛡️ Security Improvements:

  • Hardened DOM selectors using CSS.escape() for dynamic IDs
  • Proper escaping of user input in JavaScript contexts
  • Enhanced XSS protection in component and store script generation

🏗️ Architecture Refactoring:

  • Centralized Pro feature validation in ReactOnRails::Pro::Utils
  • Clean separation between core helper and Pro helper functionality
  • Updated import paths throughout codebase to reference Pro modules
  • Reorganized package exports to properly expose Pro features

Technical Changes

Ruby Side:

  • lib/react_on_rails/helper.rb: Delegates Pro functionality to Pro helper module
  • lib/react_on_rails/pro/helper.rb: Implements immediate hydration and Pro badge logic
  • lib/react_on_rails/pro/utils.rb: Centralized Pro license validation utilities
  • lib/react_on_rails/react_component/render_options.rb: Pro feature option processing

JavaScript/TypeScript Side:

  • Moved React Server Components and advanced features to node_package/src/pro/
  • Updated client startup to use immediate hydration APIs
  • Enhanced DOM selector safety with CSS.escape()
  • Reorganized package exports for Pro feature distribution

Testing:

  • Updated all test imports to reference Pro module locations
  • Added Pro license validation test stubs
  • Maintained test coverage for both core and Pro functionality

Breaking Changes

None - This refactoring maintains full backward compatibility. Existing applications will continue to work unchanged, with Pro features automatically available when a valid Pro license is detected.

Migration Notes

No action required for existing users. The refactoring is transparent and maintains all existing APIs.

Pull Request checklist

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com


This change is Reviewable

Summary by CodeRabbit

  • New Features
    • Added Pro-gated immediate hydration for components and stores, with graceful fallback and optional warning badge when unavailable.
  • Refactor
    • Renamed hydration APIs: hydrateForceLoadedStores → hydrateImmediateHydratedStores, renderOrHydrateForceLoadedComponents → renderOrHydrateImmediateHydratedComponents.
    • Separated Core and Pro runtime paths for clearer feature boundaries.
  • Security
    • Hardened DOM selectors using CSS.escape to prevent selector injection.
  • Documentation
    • Clarified licensing: Core remains MIT; Pro features are separately licensed. Added NOTICE files and updated CHANGELOG.
  • Chores
    • Updated package exports to route Pro-related entry points through a dedicated pro namespace.

Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR introduces a Core vs Pro split. It adds Pro-only helpers and utils for license-gated immediate hydration on Ruby and JS sides, moves/aliases many JS/TS modules into node_package/src/pro, updates exports in package.json, adds license/NOTICE files, hardens selectors via CSS.escape, and updates tests and configs to new pro paths.

Changes

Cohort / File(s) Summary
Licensing & Notices
LICENSE.md, CHANGELOG.md, lib/react_on_rails/pro/NOTICE, node_package/src/pro/NOTICE
Distinguish Core MIT vs Pro licensing; add Pro NOTICE files; document structural/licensing changes.
Ruby: Pro gating & helpers
lib/react_on_rails/helper.rb, lib/react_on_rails/pro/helper.rb, lib/react_on_rails/pro/utils.rb, lib/react_on_rails/react_component/render_options.rb
Introduce Pro::Helper and Pro::Utils; move immediate-hydration/store gating into Pro helpers; new methods/constants; RenderOptions integrates license gating and exposes explicitly_disabled_pro_options.
Ruby: Specs
spec/dummy/spec/helpers/react_on_rails_helper_spec.rb, spec/react_on_rails/react_component/render_options_spec.rb
Fix test string typo/format; stub license validation in specs.
Dummy app hardening
spec/dummy/client/app/startup/HelloWorldRehydratable.jsx
Use CSS.escape and quoted selectors for safer DOM queries.
JS/TS: Core entry points now use pro modules
node_package/src/ReactOnRails.client.ts, node_package/src/ReactOnRails.node.ts, node_package/src/clientStartup.ts, node_package/src/serverRenderReactComponent.ts
Redirect imports to pro counterparts; rename hydration exports to “ImmediateHydrated…”.
JS/TS: Pro modules (headers/paths/flow)
node_package/src/pro/* (…ClientSideRenderer.ts, ComponentRegistry.ts, StoreRegistry.ts, streamServerRenderedReactComponent.ts, ReactOnRailsRSC.ts, RSC*, registerServerComponent/*, wrapServerComponentRenderer/*, injectRSCPayload.ts, transformRSC*, getReactServerComponent.*, CallbackRegistry.ts, PostSSRHookTracker.ts, ServerComponentFetchError.ts)
Add license headers; adjust imports to parent dirs; add CSS.escape in selectors; rename hydration functions to ImmediateHydrated variants; add no-op server.rsc wrapper; no behavioral changes otherwise.
JS/TS: Removal of non-pro RSC wrapper
node_package/src/wrapServerComponentRenderer/server.rsc.tsx
Remove previous no-op export; superseded by pro version.
Tests updated to pro paths
node_package/tests/*
Point tests to pro modules; keep assertions/logic unchanged.
Exports & tooling config
package.json, knip.ts
Route exports to lib/pro/*; add pro entries and ignores in knip configuration.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor View as Rails View
  participant Helper as ReactOnRails::Helper
  participant ProHelper as ReactOnRails::Pro::Helper
  participant ProUtils as ReactOnRails::Pro::Utils
  participant DOM as Browser DOM
  participant Client as ClientSideRenderer (pro)

  View->>Helper: react_component(...)
  Helper->>ProHelper: generate_component_script(render_options)
  ProHelper->>ProUtils: disable_pro_render_options_if_not_licensed(options)
  ProUtils-->>ProHelper: {raw_options, explicitly_disabled_pro_options}
  ProHelper-->>View: script tag(s) (+ optional immediate-hydration inline)
  Note over View,Client: Store hydration follows same pattern via generate_store_script

  View->>DOM: Render HTML + data-attrs
  Client->>DOM: on page load
  Client->>DOM: renderOrHydrateImmediateHydratedComponents()
  Client->>DOM: hydrateImmediateHydratedStores()
  Note over Client,DOM: Selectors use data-immediate-hydration="true" and CSS.escape
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • alexeyr-ci2
  • Judahmeek

Poem

A bunny hops through folders new,
Pro paths sprout where core once grew.
Badges blink, selectors safe,
Hydration swift, yet license-gated chafe.
With NOTICE notes and exports true—
Thump-thump! The merge burrows through. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change: moving RSC (React Server Components) features into a pro/ directory and updating licensing so those files fall under the React on Rails Pro (RORP) license, which matches the file relocations, license header updates, and package export changes in the diff. It is specific, directly related to the PR objectives, and clear for someone scanning history to understand the main intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch abanoubghadban/restrict-usage-of-pro-features-to-rorp-licence

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

claude bot commented Sep 18, 2025

Code Review: Restrict usage of Pro features to RORP licensed users

Summary

This PR successfully implements restrictions on React on Rails Pro features, ensuring they're only available to licensed users. The refactoring properly separates Pro functionality into dedicated directories with appropriate licensing headers.

✅ Strengths

1. Clean Separation of Concerns

  • Excellent architectural decision to move Pro features into dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/)
  • License boundaries are clearly defined and documented

2. Proper License Headers

  • All Pro feature files include comprehensive copyright headers with references to the Pro license agreement
  • Clear documentation in LICENSE.md about the dual-licensing structure

3. Backward Compatibility

  • The refactoring maintains the existing API surface
  • License checks are properly integrated without breaking existing functionality

⚠️ Areas for Improvement

1. Missing Tests

  • No tests for the new ProFeatures::Helper module
  • Pro feature functionality like immediate_hydration lacks comprehensive test coverage
  • Consider adding tests that verify features are disabled without a valid license

2. Security Considerations

  • The license validation logic (ReactOnRails::Utils.react_on_rails_pro_licence_valid?) is referenced but not shown in the diff
  • Ensure the license validation cannot be easily bypassed on the client side
  • Consider adding server-side verification for critical Pro features

3. Documentation Updates

  • PR description lacks details about the changes
  • CHANGELOG.md needs updating
  • Consider adding migration guide for existing Pro users

🔍 Potential Issues

1. Error Handling

  • apply_immediate_hydration_if_supported and apply_store_immediate_hydration_if_supported silently fail when Pro features aren't supported
  • Consider logging or providing feedback when Pro features are attempted without a license

2. Performance

  • Multiple license checks (support_pro_features?) may impact performance if the check is expensive
  • Consider caching the license validation result

3. Code Duplication

  • apply_immediate_hydration_if_supported and apply_store_immediate_hydration_if_supported have similar patterns
  • Consider extracting common logic to reduce duplication

📋 Recommendations

  1. Add comprehensive test coverage for Pro feature restrictions
  2. Update documentation including CHANGELOG.md and migration guides
  3. Consider adding telemetry to track Pro feature usage attempts
  4. Cache license validation results to avoid repeated checks
  5. Add integration tests to ensure Pro features work correctly with valid licenses

Test Coverage Gaps

  • lib/react_on_rails/pro_features/helper.rb - No tests found
  • Pro feature JavaScript modules lack unit tests
  • Missing integration tests for license validation flow

Overall, this is a well-structured implementation that successfully restricts Pro features to licensed users. The main concerns are around test coverage and documentation, which should be addressed before merging.

Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791: Restrict usage of pro features to RORP licensed users

Overall Assessment

This PR successfully moves Pro features into dedicated directories and adds proper license headers, creating clear separation between MIT-licensed core features and React on Rails Pro features. The implementation is generally well-structured, but there are several areas that need attention.

✅ Strengths

  • Clear licensing separation: Pro features are properly isolated in lib/react_on_rails/pro_features/ and node_package/src/pro/ directories
  • Consistent license headers: All Pro files include appropriate copyright notices
  • Proper abstraction: Helper methods for Pro features are cleanly extracted into separate modules
  • Backward compatibility: Public API remains unchanged

🔴 Critical Issues

1. Missing Test Coverage

Severity: High

  • No tests added for the new Pro feature modules
  • No tests verifying license validation behavior
  • No tests ensuring Pro features are properly gated

Recommendation: Add comprehensive test coverage for:

  • ReactOnRails::ProFeatures::Helper module
  • License validation logic
  • Feature gating behavior when Pro license is missing

2. Incomplete PR Documentation

Severity: Medium

  • PR description template not filled out
  • No summary of changes provided
  • CHANGELOG entry missing as per checklist

Recommendation: Update PR description with proper summary and add CHANGELOG entry

⚠️ Code Quality Issues

1. Import Path Consistency

Location: Multiple TypeScript files

Some files use .ts extension while others use .cjs for imports. This inconsistency should be standardized across the codebase.

2. Disabled Linting Rules

File: lib/react_on_rails/helper.rb

The rubocop enable comments were removed but verify if the corresponding disable comments still exist and if the module still needs these exemptions.

3. String Mutation with gsub!

File: lib/react_on_rails/pro_features/helper.rb lines 30 and 45

Using gsub! on HTML strings is fragile and could break with different tag formats. Consider using a proper HTML parser or builder instead.

🔍 Performance Considerations

1. License Validation Caching

The support_pro_features? method calls ReactOnRails::Utils.react_on_rails_pro_licence_valid? potentially multiple times per request. Consider caching the result for the request lifecycle.

🔒 Security Considerations

1. License Validation

Ensure the license validation in ReactOnRails::Utils.react_on_rails_pro_licence_valid? is robust and cannot be easily bypassed. Consider:

  • Server-side validation only
  • Cryptographic signature verification
  • Regular license revalidation

2. Client-Side License Checks

Avoid any client-side license validation that could be bypassed through browser debugging tools.

💡 Suggestions for Improvement

  1. Add integration tests that verify Pro features work correctly with valid license and gracefully degrade without
  2. Consider using feature flags in addition to license checks for gradual rollout
  3. Add monitoring/metrics to track Pro feature usage
  4. Document the license validation flow for maintainers
  5. Add error recovery for cases where license validation fails temporarily

📝 Minor Issues

  • Some Pro files have inconsistent comment formatting in license headers
  • Consider adding a README in the pro directories explaining the licensing model
  • The warning badge HTML could be moved to a template or view partial

✅ Checklist for Approval

  • Add comprehensive test coverage
  • Complete PR description and CHANGELOG
  • Address string manipulation concerns
  • Verify all linting rules are properly configured
  • Consider adding request-level caching for license validation

Good work on the clean separation of concerns! Once the critical issues are addressed, particularly the test coverage, this PR will be ready for merge.

Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791: Restrict usage of pro features to RORP licenced users

Thank you for this PR! I've reviewed the changes and here's my comprehensive feedback:

✅ Strengths

  1. Clear separation of concerns: Moving Pro features to dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/) creates excellent architectural boundaries.

  2. Proper licensing headers: All Pro feature files now include appropriate copyright notices referencing the React on Rails Pro License.

  3. Backward compatibility maintained: The refactoring preserves the existing API surface - components using immediate_hydration will continue to work without changes.

  4. Good test coverage: The existing tests verify both licensed and unlicensed scenarios for the immediate_hydration feature.

🔍 Areas for Improvement

1. Critical Bug: Broken Import Paths

Several files have incorrect import paths that will cause runtime failures:

  • node_package/src/ReactOnRails.client.ts imports from './pro/ClientSideRenderer.ts' but should import from './ClientSideRenderer.ts'
  • node_package/src/ReactOnRails.node.ts imports from './pro/streamServerRenderedReactComponent.ts'
  • node_package/src/clientStartup.ts imports from './pro/ClientSideRenderer.ts'

These files are not in the pro/ directory and shouldn't be importing from there. This will break the entire package.

2. Logic Error in Helper Methods

In lib/react_on_rails/pro_features/helper.rb:

  • apply_immediate_hydration_if_supported only applies when BOTH immediate_hydration is true AND license is valid
  • However, pro_warning_badge_if_needed shows a warning when immediate_hydration is true but license is invalid
  • The data attribute should still be added when immediate_hydration is requested (even without license), otherwise the warning badge won't make sense

Current logic will silently fail without any indication when Pro features are used without a license.

3. Security Consideration

The license validation logic in lib/react_on_rails/utils.rb appears to be client-side verifiable. Consider:

  • Adding server-side license verification for production environments
  • Implementing telemetry or usage tracking for Pro features
  • Adding rate limiting or other protections against license bypass attempts

4. Performance Implications

The support_pro_features? method is called multiple times during rendering. Consider:

  • Caching the license check result more aggressively
  • Moving the check to initialization time rather than runtime

5. Missing Documentation

The PR lacks:

  • Update to CHANGELOG.md as mentioned in the checklist
  • Migration guide for existing Pro users
  • Documentation about the new directory structure

6. Test Gaps

Consider adding tests for:

  • The new ReactOnRails::ProFeatures::Helper module directly
  • Edge cases like malformed license files
  • Performance impact of repeated license checks

🐛 Must Fix Before Merge

  1. Fix the import paths - The current code will not run due to incorrect imports
  2. Clarify the immediate_hydration behavior - Ensure warnings are shown when unlicensed
  3. Complete the PR checklist items (tests, docs, CHANGELOG)

💡 Suggestions

  1. Consider using a feature flag system that could be extended for other Pro features
  2. Add logging/metrics to track Pro feature usage attempts
  3. Consider a grace period or degraded mode for expired licenses

Summary

This PR makes good architectural improvements but has critical issues that prevent it from working in its current state. The import path errors must be fixed, and the license enforcement logic needs refinement to ensure proper warnings are displayed to unlicensed users.

Please address the import path issues and consider the other suggestions before merging.

Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791: Restrict usage of pro features to RORP licenced users

I've reviewed the changes to restrict Pro features to licensed users. Here's my comprehensive feedback:

✅ Strengths

  1. Clear separation of concerns: Moving Pro features to dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/) provides excellent code organization.

  2. Consistent licensing headers: All Pro files have proper copyright notices clearly indicating their proprietary nature.

  3. Non-breaking changes: The refactoring maintains backward compatibility by keeping the public API surface intact.

🔍 Areas for Improvement

1. Security & License Validation 🔐

  • The support_pro_features? method relies on ReactOnRails::Utils.react_on_rails_pro_licence_valid? but this validation method isn't shown in the diff. Ensure it has robust validation logic that can't be easily bypassed.
  • Consider implementing server-side license verification to prevent client-side tampering.

2. Performance Considerations

  • The license check happens on every component render. Consider caching the license validation result at application startup to avoid repeated checks.
  • The warning badge HTML is generated on every call to pro_warning_badge_if_needed. Consider memoizing this.

3. Code Quality Issues 🐛

In lib/react_on_rails/pro_features/helper.rb:

  • Lines 30 & 45: Using gsub! to modify HTML strings is fragile. Consider using proper DOM manipulation or template helpers.
  • The strip_heredoc method (line 49) might not be available in all Rails versions. Use squish or ensure compatibility.

Import path inconsistencies:

  • Some files use relative imports (../types/index.ts) while others use absolute. Standardize on one approach.

4. Test Coverage 🧪

  • No test files are included in this PR. Critical areas needing tests:
    • License validation logic
    • Fallback behavior when Pro features are used without a license
    • Component rendering with/without immediate hydration
    • Error handling when Pro features fail

5. Documentation 📚

  • The LICENSE.md update is good, but consider adding:
    • Migration guide for existing Pro users
    • Clear documentation on which features require Pro license
    • Setup instructions for Pro license configuration

🚨 Potential Bugs

  1. Race condition in immediate hydration: The client-side code checks for typeof ReactOnRails === 'object' but doesn't verify if the necessary methods exist before calling them.

  2. Missing error boundaries: If Pro features fail to load, there's no graceful degradation strategy visible in the code.

  3. Bundle size impact: Moving core functionality to Pro directories might affect tree-shaking. Verify that non-Pro users don't bundle Pro code.

📋 Recommended Actions

  1. Add comprehensive tests for all Pro feature restrictions
  2. Implement license caching to improve performance
  3. Add error boundaries around Pro feature usage
  4. Include migration documentation for existing users
  5. Verify bundle splitting works correctly for Pro vs non-Pro builds
  6. Consider adding feature flags for gradual rollout

💡 Suggestions

  • Consider using feature detection instead of license checks in development mode for better DX
  • Add telemetry to track Pro feature usage (with user consent)
  • Implement a grace period for expired licenses to avoid breaking production apps

Overall, this is a good foundation for separating Pro features, but it needs additional work on testing, performance optimization, and error handling before merging.

Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791: Restrict usage of pro features to RORP licensed users

Thank you for working on this important licensing enforcement feature. I've reviewed the changes and have several observations and recommendations.

✅ Strengths

  1. Clean Separation: The extraction of Pro features into dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/) provides clear architectural boundaries between core and Pro functionality.

  2. Consistent License Headers: All Pro feature files now include proper copyright notices and license references, which is crucial for license enforcement.

  3. Backward Compatibility: The refactoring maintains backward compatibility by preserving the same public API surface while adding license validation behind the scenes.

  4. Comprehensive Test Coverage: The existing test suite includes tests for both licensed and unlicensed scenarios, ensuring the feature behaves correctly in both cases.

⚠️ Areas for Improvement

1. PR Description Incomplete

The PR description needs to be completed. Please:

  • Remove placeholder text and add a meaningful summary
  • Update the checklist items (tests, documentation, CHANGELOG)
  • Add a CHANGELOG entry at the top of the file

2. Import Path Updates

Some TypeScript imports still use relative paths instead of absolute paths from the types directory:

  • node_package/src/pro/ClientSideRenderer.ts:4 uses '../types/index.ts' - consider using consistent import style

3. Package Exports Configuration

The package.json exports are correctly updated to reference the new Pro directory structure. However, ensure that build scripts are updated to compile these new locations.

4. Missing Documentation Updates

The immediate hydration feature and Pro licensing requirements should be documented in:

  • README.md
  • User-facing documentation
  • Migration guide for users moving from open source to Pro

🔍 Potential Issues

1. License Validation Performance

The support_pro_features? method is called multiple times during rendering. Consider caching the result at the request level to avoid repeated license checks:

def support_pro_features?
  @support_pro_features ||= ReactOnRails::Utils.react_on_rails_pro_licence_valid?
end

2. Warning Badge Display

The Pro warning badge is shown when immediate_hydration is enabled without a valid license. However, the badge might interfere with legitimate page content (fixed positioning at top-right). Consider:

  • Making it dismissible
  • Using a less intrusive notification method
  • Adding a config option to disable the visual warning

3. Error Handling

The Pro features silently fail when no license is present. Consider adding development-mode warnings in logs to help developers understand why features aren't working.

🔒 Security Considerations

  1. License Validation: The license validation logic appears to be server-side only, which is appropriate. Ensure no client-side bypasses are possible.

  2. No Sensitive Data: The PR correctly avoids exposing license keys or sensitive validation logic in client-side code.

🎯 Performance Considerations

  1. Bundle Size: Moving core registries to the Pro directory might affect tree-shaking for open-source users. Verify that unused Pro code is properly eliminated from production bundles.

  2. Lazy Loading: Consider lazy-loading Pro features only when needed to minimize the impact on initial page load.

📋 Recommendations

  1. Complete the PR description with proper summary, checklist updates, and CHANGELOG entry
  2. Add documentation for the licensing changes and migration path
  3. Consider caching license validation results per request
  4. Add development warnings when Pro features are attempted without a license
  5. Test bundle size impact on open-source users
  6. Consider making the warning badge less intrusive or configurable

✅ Testing

The existing test suite properly mocks license validation and tests both licensed and unlicensed scenarios. Good coverage! Consider adding:

  • Integration tests for the warning badge display
  • Bundle size regression tests
  • Performance tests for license validation

Overall, this is a well-structured implementation of license enforcement. With the improvements suggested above, particularly around documentation and the PR description, this will be ready to merge.

🤖 Generated with Claude Code

Copy link
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: 6

Caution

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

⚠️ Outside diff range comments (5)
node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts (1)

81-86: Don’t call controller.close() after controller.error()

Closing a ReadableStream after signaling error violates the Streams API and can throw. Track the error state and only close when not errored.

Apply:

-      } catch (error) {
-        console.error('Error transforming RSC stream:', error);
-        controller.error(error);
-      } finally {
-        controller.close();
-      }
+      } catch (error) {
+        console.error('Error transforming RSC stream:', error);
+        controller.error(error);
+        // fall through to finally without closing
+      } finally {
+        // Only close if we didn't error
+        try {
+          // close() will throw if already errored; guard via try
+          controller.close();
+        } catch {
+          /* noop */
+        }
+      }
node_package/src/pro/RSCRequestTracker.ts (2)

32-38: Type mismatch: NodeJS.ReadableStream lacks .pipe/.destroy in TS.

This will either fail type-checking or force unsafe casts. Use stream.Readable for both the global declaration and method return.

Apply:

 declare global {
   function generateRSCPayload(
     componentName: string,
     props: unknown,
     railsContext: RailsContextWithServerComponentMetadata,
-  ): Promise<NodeJS.ReadableStream>;
+  ): Promise<Readable>;
 }
@@
-async getRSCPayloadStream(componentName: string, props: unknown): Promise<NodeJS.ReadableStream> {
+async getRSCPayloadStream(componentName: string, props: unknown): Promise<Readable> {

Also applies to: 122-122


132-143: Harden stream piping: handle errors and cleanup tees.

If the source stream emits 'error', it can crash the process or leak listeners. Add a one-time error handler and ensure both tees are destroyed.

-    try {
-      const stream = await generateRSCPayload(componentName, props, this.railsContext);
+    try {
+      const stream = await generateRSCPayload(componentName, props, this.railsContext);
@@
-      const stream1 = new PassThrough();
-      stream.pipe(stream1);
-      const stream2 = new PassThrough();
-      stream.pipe(stream2);
+      const stream1 = new PassThrough();
+      const stream2 = new PassThrough();
+      stream.pipe(stream1);
+      stream.pipe(stream2);
+      stream.once('error', (err) => {
+        // Propagate to consumers and prevent leaks
+        stream1.destroy(err as Error);
+        stream2.destroy(err as Error);
+      });
+      stream.once('end', () => {
+        // Optional: drop reference to allow GC earlier
+        // no-op; rely on clear() if you prefer explicit lifecycle
+      });
node_package/src/pro/ClientSideRenderer.ts (2)

98-113: Missing Pro gating for force‑loaded components (regression risk)

Current gating only covers data-immediate-hydration. As learned previously, both data-force-load="true" and hydrating while document.readyState === "loading" must require Pro. Without gating here, non‑Pro users can still force‑load components immediately.

Apply:

-    const isImmediateHydrationRequested = el.getAttribute('data-immediate-hydration') === 'true';
-    const hasProLicense = railsContext.rorPro;
-
-    // Handle immediate_hydration feature usage without Pro license
-    if (isImmediateHydrationRequested && !hasProLicense) {
-      console.warn(IMMEDIATE_HYDRATION_PRO_WARNING);
-
-      // Fallback to standard behavior: wait for page load before hydrating
-      if (document.readyState === 'loading') {
-        await new Promise<void>((resolve) => {
-          onPageLoaded(resolve);
-        });
-      }
-    }
+    const isImmediateHydrationRequested = el.getAttribute('data-immediate-hydration') === 'true';
+    const isComponentForceLoaded = el.getAttribute('data-force-load') === 'true';
+    const isLoading = document.readyState === 'loading';
+    const hasProLicense = railsContext.rorPro;
+
+    // Pro gating for early hydration paths
+    if (!hasProLicense) {
+      if (isImmediateHydrationRequested) {
+        console.warn(IMMEDIATE_HYDRATION_PRO_WARNING);
+        if (isLoading) {
+          await new Promise<void>((resolve) => onPageLoaded(resolve));
+        }
+      } else if (isComponentForceLoaded || isLoading) {
+        // Force-loaded components or hydrating during 'loading' require Pro
+        console.warn(FORCE_LOAD_PRO_WARNING);
+        if (isLoading) {
+          await new Promise<void>((resolve) => onPageLoaded(resolve));
+        }
+      }
+    }

215-228: Missing Pro gating for force‑loaded stores

Stores with data-force-load="true" (or hydration while page is still loading) should gate similarly. Otherwise non‑Pro users can hydrate stores early.

Apply:

   constructor(storeDataElement: Element) {
     this.state = 'hydrating';
     const railsContext = getRailsContext();
     if (!railsContext) {
       return;
     }
 
     const name = storeDataElement.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || '';
     const props =
       storeDataElement.textContent !== null
         ? (JSON.parse(storeDataElement.textContent) as Record<string, unknown>)
         : {};
-    this.hydratePromise = this.hydrate(railsContext, name, props);
+    const isForceLoaded = storeDataElement.getAttribute('data-force-load') === 'true';
+    const isLoading = document.readyState === 'loading';
+    if (!railsContext.rorPro && (isForceLoaded || isLoading)) {
+      console.warn(STORE_FORCE_LOAD_PRO_WARNING);
+      this.hydratePromise = new Promise<void>((resolve) => onPageLoaded(resolve)).then(() => {
+        if (this.state === 'unmounted') return;
+        return this.hydrate(railsContext, name, props);
+      });
+    } else {
+      this.hydratePromise = this.hydrate(railsContext, name, props);
+    }
🧹 Nitpick comments (21)
node_package/src/pro/transformRSCNodeStream.ts (2)

1-14: Preserve the Pro license header in built bundles

Minifiers often drop comments. Use a preserved header to ensure compliance and double‑check the URL path is correct in this repo/branch.

-/*
+/*! @license @preserve
  * Copyright (c) 2025 Shakacode
  *
  * This file, and all other files in this directory, are NOT licensed under the MIT license.
  *
  * This file is part of React on Rails Pro.
  *
  * Unauthorized copying, modification, distribution, or use of this file, via any medium,
  * is strictly prohibited. It is proprietary and confidential.
  *
  * For the full license agreement, see:
  * https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md
  */

31-59: Stream‑safe UTF‑8 decoding and defensive flush

Decoding per chunk without streaming risks breaking multi‑byte chars across chunk boundaries. Recommend streaming decode and a flush to handle any trailing bytes; safe even if upstream always ends lines with “\n” (per prior learning), and future‑proofs the code.

-import { Transform } from 'stream';
+import { Transform } from 'stream';
+import { TextDecoder as NodeTextDecoder } from 'util';
@@
-export default function transformRSCStream(stream: NodeJS.ReadableStream): NodeJS.ReadableStream {
-  const decoder = new TextDecoder();
+export default function transformRSCStream(stream: NodeJS.ReadableStream): NodeJS.ReadableStream {
+  const decoder = new NodeTextDecoder();
   let lastIncompleteChunk = '';
@@
   const htmlExtractor = new Transform({
     transform(oneOrMoreChunks, _, callback) {
       try {
-        const decodedChunk = lastIncompleteChunk + decoder.decode(oneOrMoreChunks as Uint8Array);
+        const decodedChunk =
+          lastIncompleteChunk +
+          decoder.decode(oneOrMoreChunks as Uint8Array, { stream: true });
         const separateChunks = decodedChunk.split('\n').filter((chunk) => chunk.trim() !== '');
@@
         callback();
       } catch (error) {
         callback(error as Error);
       }
     },
+    flush(callback) {
+      try {
+        const trailing = lastIncompleteChunk + decoder.decode(); // flush internal decoder state
+        if (trailing.trim() !== '') {
+          const remaining = trailing.split('\n').filter((c) => c.trim() !== '');
+          for (const chunk of remaining) {
+            const parsedData = JSON.parse(chunk) as { html: string };
+            this.push(parsedData.html);
+          }
+        }
+        callback();
+      } catch (error) {
+        callback(error as Error);
+      }
+    },
   });

Note: If you prefer Node’s StringDecoder, the same approach applies.
Reference: prior learning says flush wasn’t needed because all chunks end with “\n”; this change is defensive, not urgent.

node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts (2)

58-61: Use streaming decode to avoid splitting multi‑byte characters across chunks

Using TextDecoder without { stream: true } risks invalid JSON when a UTF‑8 sequence spans chunk boundaries.

-          const decodedValue = typeof value === 'string' ? value : decoder.decode(value);
+          const decodedValue =
+            typeof value === 'string' ? value : decoder.decode(value, { stream: true });

49-53: Gate console replay script injection for production/CSP

Injecting inline scripts may violate CSP and is unnecessary in production. Gate by env and add nonce support if available.

-        if (replayConsoleCode?.trim() !== '') {
-          const scriptElement = document.createElement('script');
-          scriptElement.textContent = replayConsoleCode;
-          document.body.appendChild(scriptElement);
-        }
+        if (
+          replayConsoleCode.trim() !== '' &&
+          typeof document !== 'undefined' &&
+          process.env.NODE_ENV !== 'production'
+        ) {
+          const scriptElement = document.createElement('script');
+          // Optionally set nonce from a meta tag or global if present:
+          // const nonce = document.querySelector('meta[name="csp-nonce"]')?.getAttribute('content')
+          // if (nonce) scriptElement.setAttribute('nonce', nonce);
+          scriptElement.textContent = replayConsoleCode;
+          document.body.appendChild(scriptElement);
+        }
node_package/src/pro/PostSSRHookTracker.ts (1)

36-43: JSDoc says “@throws” but implementation logs and returns

Either update docs or throw. Current mismatch can mislead users.

-   * @throws Error if called after SSR has already ended
+   * If called after SSR has ended, logs an error and no‑ops.
node_package/src/pro/ServerComponentFetchError.ts (1)

26-32: Preserve error causality via ErrorOptions.cause

Use the standard cause to keep proper stacks and tooling support.

-  constructor(message: string, componentName: string, componentProps: unknown, originalError: Error) {
-    super(message);
+  constructor(message: string, componentName: string, componentProps: unknown, originalError: Error) {
+    super(message, { cause: originalError });
     this.name = 'ServerComponentFetchError';
     this.serverComponentName = componentName;
     this.serverComponentProps = componentProps;
     this.originalError = originalError;
   }
node_package/src/pro/getReactServerComponent.client.ts (1)

105-111: Preserve Array.prototype.push semantics when overriding

Return the new array length and keep the backing array updated to avoid surprising callers.

-      // eslint-disable-next-line no-param-reassign
-      payloads.push = (...chunks) => {
-        chunks.forEach(handleChunk);
-        return chunks.length;
-      };
+      // eslint-disable-next-line no-param-reassign
+      const originalPush = payloads.push.bind(payloads);
+      payloads.push = (...chunks) => {
+        chunks.forEach(handleChunk);
+        return originalPush(...chunks);
+      };
node_package/src/pro/CallbackRegistry.ts (1)

50-75: Potential “used before assignment” and unsafe clearTimeout

timeoutId may remain unassigned if no registryTimeout is set. Type as optional and guard before clearing.

-    let timeoutId: NodeJS.Timeout;
+    let timeoutId: ReturnType<typeof setTimeout> | undefined;
@@
-      clearTimeout(timeoutId);
+      if (timeoutId) clearTimeout(timeoutId);
node_package/src/pro/RSCRequestTracker.ts (1)

150-154: Isolate callback failures.

One faulty callback should not break others or the main flow.

-      this.callbacks.forEach((callback) => callback(streamInfo));
+      this.callbacks.forEach((callback, i) => {
+        try {
+          callback(streamInfo);
+        } catch (err) {
+          console.warn(`RSC payload callback[${i}] threw:`, err);
+        }
+      });
node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx (1)

15-20: Make the noop export explicit and named.

Small clarity/TS hinting improvement; behavior unchanged.

-const wrapServerComponentRenderer = () => {
+export default function wrapServerComponentRenderer(): void {
   // Do nothing
   // Server component renderers are client components and not needed in the RSC bundle
-};
-
-export default wrapServerComponentRenderer;
+}
LICENSE.md (1)

42-47: Avoid duplication drift of Pro paths.

Consider also listing built outputs (e.g., node_package/lib/pro/) or stating “and their built artifacts” to prevent ambiguity.

-The code in the directories listed above is part of the React on Rails Pro framework and is licensed under the React on Rails Pro License.
+The code in the directories listed above, and their built artifacts (e.g., node_package/lib/pro/), is part of the React on Rails Pro framework and is licensed under the React on Rails Pro License.
node_package/src/pro/registerServerComponent/server.tsx (1)

41-45: Nit: avoid creating entry tuples when you only need keys.

Slightly simpler and avoids a tiny allocation.

-  for (const [componentName] of Object.entries(components)) {
+  for (const componentName of Object.keys(components)) {
node_package/src/pro/ComponentRegistry.ts (1)

47-51: Doc comment mismatch with actual property name.

The returned object uses renderFunction, not isRenderFunction.

- * @returns { name, component, isRenderFunction, isRenderer }
+ * @returns { name, component, renderFunction, isRenderer }
node_package/src/pro/injectRSCPayload.ts (1)

75-83: Optional: annotate return type for clarity.

Helps consumers and prevents accidental signature drift.

-export default function injectRSCPayload(
+export default function injectRSCPayload(
   pipeableHtmlStream: PipeableOrReadableStream,
   rscRequestTracker: RSCRequestTracker,
   domNodeId: string | undefined,
-) {
+): NodeJS.ReadableStream {
node_package/src/pro/ClientSideRenderer.ts (1)

31-34: Add Pro warning constants for force_load features

We warn for immediate_hydration, but there’s no analogous warning for force_load (components/stores). Add explicit constants for clearer messaging.

Apply:

 const IMMEDIATE_HYDRATION_PRO_WARNING =
   "[REACT ON RAILS] The 'immediate_hydration' feature requires a React on Rails Pro license. " +
   'Please visit https://shakacode.com/react-on-rails-pro to get a license.';
+
+const FORCE_LOAD_PRO_WARNING =
+  "[REACT ON RAILS] The 'force_load' feature requires a React on Rails Pro license. " +
+  'Please visit https://shakacode.com/react-on-rails-pro to get a license.';
+
+const STORE_FORCE_LOAD_PRO_WARNING =
+  "[REACT ON RAILS] Store 'force_load' requires a React on Rails Pro license. " +
+  'Please visit https://shakacode.com/react-on-rails-pro to get a license.';
node_package/tests/registerServerComponent.client.test.jsx (1)

154-244: Please add tests for Pro gating of force_load and immediate_hydration

Missing unit tests for new gating in ClientSideRenderer (components and stores).

I can draft tests that:

  • set railsContext.rorPro = false
  • mark elements/stores with data-force-load="true"
  • assert no hydration until DOMContentLoaded, and warn messages emitted.
lib/react_on_rails/helper.rb (3)

665-668: Minor: keep attribute injection localized to tag creation.

The apply_* helper mutates the rendered tag string. That’s fine, but consider pushing the “data-immediate-hydration” attribute into the original content_tag call when supported, to avoid later string surgery. Non-blocking.


454-454: Fix RuboCop: remove extra blank line.

Pipeline flags Layout/EmptyLines at Line 454. Remove the stray blank line.

Apply this diff near Line 454:

- 

3-4: Re-enable disabled cops.

Rubocop warns Lint/MissingCopEnableDirective for Metrics/ModuleLength and Metrics/MethodLength. Add enable directives at file end.

Apply this diff near the end of the file (before the final ends or right after Line 829):

+# rubocop:enable Metrics/ModuleLength
+# rubocop:enable Metrics/MethodLength
lib/react_on_rails/pro_features/helper.rb (2)

67-69: Prefer Rails logger only; avoid stdout noise.

puts will spam STDOUT in production. Keep the warning via Rails.logger.warn only.

Apply this diff:

-        puts IMMEDIATE_HYDRATION_PRO_WARNING
         Rails.logger.warn IMMEDIATE_HYDRATION_PRO_WARNING

63-82: UX nit: badge accessibility.

Add aria-label on the anchor and role="note" on the ribbon div for screen readers.

Apply this diff:

-          <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}">
+          <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}" aria-label="#{tooltip_text}">
             <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;">
-              <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
+              <div role="note" style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b14683 and 6a6bd67.

📒 Files selected for processing (37)
  • LICENSE.md (2 hunks)
  • lib/react_on_rails/helper.rb (3 hunks)
  • lib/react_on_rails/pro_features/helper.rb (1 hunks)
  • node_package/src/ReactOnRails.client.ts (1 hunks)
  • node_package/src/ReactOnRails.node.ts (1 hunks)
  • node_package/src/clientStartup.ts (1 hunks)
  • node_package/src/pro/CallbackRegistry.ts (1 hunks)
  • node_package/src/pro/ClientSideRenderer.ts (1 hunks)
  • node_package/src/pro/ComponentRegistry.ts (1 hunks)
  • node_package/src/pro/PostSSRHookTracker.ts (1 hunks)
  • node_package/src/pro/RSCProvider.tsx (1 hunks)
  • node_package/src/pro/RSCRequestTracker.ts (1 hunks)
  • node_package/src/pro/RSCRoute.tsx (1 hunks)
  • node_package/src/pro/ReactOnRailsRSC.ts (3 hunks)
  • node_package/src/pro/ServerComponentFetchError.ts (1 hunks)
  • node_package/src/pro/StoreRegistry.ts (1 hunks)
  • node_package/src/pro/getReactServerComponent.client.ts (1 hunks)
  • node_package/src/pro/getReactServerComponent.server.ts (1 hunks)
  • node_package/src/pro/injectRSCPayload.ts (1 hunks)
  • node_package/src/pro/registerServerComponent/client.tsx (1 hunks)
  • node_package/src/pro/registerServerComponent/server.rsc.ts (1 hunks)
  • node_package/src/pro/registerServerComponent/server.tsx (1 hunks)
  • node_package/src/pro/streamServerRenderedReactComponent.ts (2 hunks)
  • node_package/src/pro/transformRSCNodeStream.ts (1 hunks)
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts (1 hunks)
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx (1 hunks)
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx (1 hunks)
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx (1 hunks)
  • node_package/src/serverRenderReactComponent.ts (1 hunks)
  • node_package/src/wrapServerComponentRenderer/server.rsc.tsx (0 hunks)
  • node_package/tests/ComponentRegistry.test.js (1 hunks)
  • node_package/tests/StoreRegistry.test.js (1 hunks)
  • node_package/tests/injectRSCPayload.test.ts (1 hunks)
  • node_package/tests/registerServerComponent.client.test.jsx (2 hunks)
  • node_package/tests/serverRenderReactComponent.test.ts (1 hunks)
  • node_package/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • package.json (1 hunks)
💤 Files with no reviewable changes (1)
  • node_package/src/wrapServerComponentRenderer/server.rsc.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for all JavaScript/TypeScript code

Files:

  • node_package/src/pro/ServerComponentFetchError.ts
  • node_package/src/pro/transformRSCNodeStream.ts
  • node_package/src/clientStartup.ts
  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/tests/serverRenderReactComponent.test.ts
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/tests/streamServerRenderedReactComponent.test.jsx
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/tests/registerServerComponent.client.test.jsx
  • node_package/tests/injectRSCPayload.test.ts
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/serverRenderReactComponent.ts
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/tests/ComponentRegistry.test.js
  • node_package/src/pro/PostSSRHookTracker.ts
  • node_package/src/pro/StoreRegistry.ts
  • node_package/src/pro/CallbackRegistry.ts
  • node_package/src/pro/ComponentRegistry.ts
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx
  • node_package/src/pro/RSCRequestTracker.ts
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts
  • node_package/tests/StoreRegistry.test.js
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • node_package/src/pro/streamServerRenderedReactComponent.ts
node_package/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place NPM package client-side source under node_package/src

Files:

  • node_package/src/pro/ServerComponentFetchError.ts
  • node_package/src/pro/transformRSCNodeStream.ts
  • node_package/src/clientStartup.ts
  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/serverRenderReactComponent.ts
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/src/pro/PostSSRHookTracker.ts
  • node_package/src/pro/StoreRegistry.ts
  • node_package/src/pro/CallbackRegistry.ts
  • node_package/src/pro/ComponentRegistry.ts
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx
  • node_package/src/pro/RSCRequestTracker.ts
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • node_package/src/pro/streamServerRenderedReactComponent.ts
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Use RuboCop for all Ruby code

Files:

  • lib/react_on_rails/pro_features/helper.rb
  • lib/react_on_rails/helper.rb
lib/**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Place Ruby gem source under lib/ (e.g., lib/react_on_rails/*)

Files:

  • lib/react_on_rails/pro_features/helper.rb
  • lib/react_on_rails/helper.rb
🧠 Learnings (18)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • node_package/src/pro/ServerComponentFetchError.ts
  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/tests/streamServerRenderedReactComponent.test.jsx
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/pro/RSCRequestTracker.ts
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • node_package/src/pro/streamServerRenderedReactComponent.ts
📚 Learning: 2025-04-09T13:02:16.009Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.

Applied to files:

  • node_package/src/pro/transformRSCNodeStream.ts
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts
📚 Learning: 2025-09-16T02:48:43.027Z
Learnt from: CR
PR: shakacode/react_on_rails#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T02:48:43.027Z
Learning: Applies to node_package/src/**/*.{ts,tsx,js,jsx} : Place NPM package client-side source under node_package/src

Applied to files:

  • node_package/src/clientStartup.ts
  • node_package/src/ReactOnRails.client.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • node_package/src/clientStartup.ts
  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/tests/ComponentRegistry.test.js
  • node_package/src/pro/PostSSRHookTracker.ts
  • node_package/src/pro/CallbackRegistry.ts
  • node_package/src/pro/ComponentRegistry.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • node_package/src/pro/streamServerRenderedReactComponent.ts
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/tests/serverRenderReactComponent.test.ts
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/tests/streamServerRenderedReactComponent.test.jsx
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/tests/registerServerComponent.client.test.jsx
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/serverRenderReactComponent.ts
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/src/pro/ComponentRegistry.ts
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • node_package/src/pro/streamServerRenderedReactComponent.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx
  • node_package/src/pro/RSCRequestTracker.ts
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/src/pro/RSCRequestTracker.ts
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/RSCRequestTracker.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • lib/react_on_rails/pro_features/helper.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/src/pro/RSCRequestTracker.ts
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
PR: shakacode/react_on_rails#1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • package.json
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • LICENSE.md
  • package.json
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/ReactOnRails.node.ts
  • node_package/src/pro/getReactServerComponent.server.ts
  • lib/react_on_rails/pro_features/helper.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-06-11T12:34:58.182Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCProvider.tsx:0-0
Timestamp: 2025-06-11T12:34:58.182Z
Learning: `RSCProvider` intentionally keeps failed `getServerComponent` promises cached (even if rejected) to avoid repeated fetch attempts; callers must use `refetchComponent` to retry.

Applied to files:

  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/pro/RSCRoute.tsx
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • node_package/src/ReactOnRails.node.ts
  • node_package/src/ReactOnRails.client.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • package.json
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • lib/react_on_rails/helper.rb
🧬 Code graph analysis (2)
lib/react_on_rails/pro_features/helper.rb (2)
lib/react_on_rails/react_component/render_options.rb (2)
  • immediate_hydration (98-100)
  • dom_id (48-56)
lib/react_on_rails/utils.rb (1)
  • react_on_rails_pro_licence_valid? (216-229)
lib/react_on_rails/helper.rb (2)
lib/react_on_rails/react_component/render_options.rb (1)
  • store_dependencies (138-140)
lib/react_on_rails/pro_features/helper.rb (2)
  • apply_immediate_hydration_if_supported (27-39)
  • apply_store_immediate_hydration_if_supported (42-55)
🪛 GitHub Actions: Lint JS and Ruby
lib/react_on_rails/helper.rb

[warning] 3-3: bundle exec rubocop: Lint/MissingCopEnableDirective: Re-enable Metrics/ModuleLength cop with # rubocop:enable after disabling it.


[warning] 4-4: bundle exec rubocop: Lint/MissingCopEnableDirective: Re-enable Metrics/MethodLength cop with # rubocop:enable after disabling it.


[error] 454-454: bundle exec rubocop: Layout/EmptyLines: Extra blank line detected. [Correctable]

🔇 Additional comments (42)
node_package/src/pro/RSCRoute.tsx (1)

1-13: License header addition — LGTM

Header text and link look consistent across pro/* files.

node_package/src/pro/RSCProvider.tsx (1)

17-17: Import path update — LGTM

Centralizing utils under ../utils.ts is consistent with the PR.

node_package/src/pro/getReactServerComponent.server.ts (1)

18-20: Path realignments — LGTM

loadJsonFile and types moved to parent dir; no behavior change.

node_package/src/pro/getReactServerComponent.client.ts (1)

17-19: Import path updates — LGTM

Consolidation to ../utils.ts and ../types/index.ts matches the new layout.

node_package/src/pro/RSCRequestTracker.ts (2)

1-14: License header addition looks good.

Consistent with Pro gating across src/pro.


16-16: Import path realignment is correct.

Points to shared utils/types outside pro folder; keeps one source of truth.

Please confirm all other src/pro/* files reference ../types and ../utils consistently to avoid duplicate bundles.

Also applies to: 21-21

node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx (1)

1-13: License header addition looks good.

node_package/src/pro/ReactOnRailsRSC.ts (2)

1-14: License header addition looks good.


24-27: Verified — package.json maps RSC export correctly

package.json contains "react-server": "./node_package/lib/pro/ReactOnRailsRSC.js" (line 9); additional react-server entries point to registerServerComponent/server.rsc.js and wrapServerComponentRenderer/server.rsc.js.

LICENSE.md (1)

8-14: Clear distinction between Core (MIT) and Pro.

Good call-out of pro directories.

node_package/tests/serverRenderReactComponent.test.ts (1)

3-3: Updated to pro ComponentRegistry import — OK.

Matches the reorg to src/pro.

node_package/src/pro/StoreRegistry.ts (2)

1-14: License header addition looks good.


16-16: Type import path update is correct.

Keeps types centralized under ../types.

node_package/tests/ComponentRegistry.test.js (1)

9-9: Updated to pro ComponentRegistry import — OK.

Mock still targets ../src/pageLifecycle.ts, which aligns with pro/ComponentRegistry importing from ../pageLifecycle.ts.

If pro/ComponentRegistry ever inlines/relocates pageLifecycle, update the mock path accordingly.

node_package/tests/streamServerRenderedReactComponent.test.jsx (1)

7-8: Switched to pro imports — OK.

Consistent with src/pro move.

node_package/src/pro/registerServerComponent/server.tsx (2)

1-13: License header added — looks correct.

Header text and link format are consistent with other Pro files.


16-19: Import path updates are correct relative to this location.

../../ReactOnRails.client.ts and ../../types/index.ts resolve properly from pro/registerServerComponent/.

node_package/src/pro/registerServerComponent/server.rsc.ts (2)

1-13: License header added — OK.


15-17: Imports repointed upward — OK.

Using the client registry in RSC bundle is intentional here.

node_package/tests/StoreRegistry.test.js (1)

3-3: Test import moved to Pro registry — OK.

Covers the same behaviors with the Pro path.

node_package/src/pro/ComponentRegistry.ts (2)

1-13: License header added — OK.


15-17: Import path adjustments — OK.

node_package/src/ReactOnRails.node.ts (1)

2-2: Node streaming now uses Pro implementation — OK.

Matches PR objective to restrict streaming SSR to licensed users.

node_package/src/pro/injectRSCPayload.ts (2)

1-13: License header added — OK.


17-19: Import path realignments — OK.

Refs now correctly point to ../utils and ../types.

node_package/src/serverRenderReactComponent.ts (1)

4-4: Swapping to Pro ComponentRegistry aligns with gating goals — verified.
No functional changes; confirmed there are no imports of the non‑Pro registry under node_package/src (all references point to ./pro/ComponentRegistry.ts).

node_package/src/pro/registerServerComponent/client.tsx (2)

1-13: License header addition looks good

Proprietary header aligns with Pro gating rollout.


16-19: Import path realignment is correct

Paths now consistently point to shared core and pro modules; matches the server-side counterpart.

node_package/src/pro/wrapServerComponentRenderer/server.tsx (2)

1-13: License header: OK

Matches Pro licensing scheme across files.


16-21: Imports adjusted correctly

Types/utilities now resolve from ../../; no behavior change.

node_package/tests/injectRSCPayload.test.ts (1)

3-4: Test imports updated to Pro paths

Keeps tests aligned with new module layout.

node_package/src/pro/ClientSideRenderer.ts (1)

274-314: Force‑load selectors are fine if renderers self‑gate

Leaving these selectors unconditional is OK once renderers/stores defer when not licensed (above diffs). No change needed here.

Add tests asserting that with railsContext.rorPro = false:

  • components/stores marked data-force-load="true" don’t hydrate until DOMContentLoaded
  • immediate_hydration waits and warns.
node_package/src/pro/wrapServerComponentRenderer/client.tsx (2)

1-13: License header: OK

Consistent with Pro files.


17-20: Path updates look correct

Types/utilities now resolve from ../../. Behavior unchanged.

node_package/src/pro/streamServerRenderedReactComponent.ts (3)

1-13: License header: OK

Matches the Pro licensing pattern.


19-34: Import relocations: OK

Core utilities now imported from ../; no functional changes. Stream pipeline remains intact.


111-153: Stream buffering wrapper is sound

No changes here, but noting: buffering avoids early error crashes and preserves event order. Good practice for SSR streams.

node_package/tests/registerServerComponent.client.test.jsx (2)

15-16: Tests updated to Pro paths

Keeps RSC tests aligned with Pro modules. Good.


39-49: React.use guard test is valuable

Ensures ensureReactUseAvailable fails loudly when React 19 APIs are unavailable.

lib/react_on_rails/helper.rb (3)

14-20: Good extraction: centralize Pro gating via include.

Requiring and including ProFeatures::Helper keeps core helper clean and consistent with the PR goal.


650-654: Correct: preserve requested flag but gate actual behavior.

Capturing immediate_hydration_requested before forcing false when Pro isn’t supported lets you show the badge while preventing use. Solid approach.


685-689: OK: store hydration delegated to Pro helper.

Separation of concerns looks good. Follow-up in Pro helper about attribute/script safety.

Please run the test that covers store immediate hydration to confirm no behavior change after extraction.

renderOrHydrateForceLoadedComponents,
unmountAll,
} from './ClientSideRenderer.ts';
} from './pro/ClientSideRenderer.ts';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Double‑check exports/packaging so OSS consumers don’t import Pro code by accident

Importing from ./pro/ClientSideRenderer.ts is fine if package.json exports map and build config route OSS users appropriately. Verify ESM/CJS entries and TS pathing.

Run to confirm mapping and occurrences:


🏁 Script executed:

#!/bin/bash
fd package.json | xargs -I{} jq -r '.exports? // empty' {} 2>/dev/null | sed -n '1,200p'
rg -nP 'ClientSideRenderer\.ts' -C2 node_package

Length of output: 1988


Pro modules are publicly exported — restrict or confirm intent

  • package.json exports map exposes many ./node_package/lib/pro/* entries (e.g. "./registerServerComponent/client" -> ./node_package/lib/pro/registerServerComponent/client.js).
  • Source imports reference pro implementation: node_package/src/clientStartup.ts:7 and node_package/src/ReactOnRails.client.ts:3 import './pro/ClientSideRenderer.ts'.
  • Action: remove/alias ./pro/* entries from package.json exports or adjust the build/exports so OSS consumers resolve to non‑pro implementations; if exposure is intentional, document and gate access.
🤖 Prompt for AI Agents
In node_package/src/clientStartup.ts around line 7 (and similarly
node_package/src/ReactOnRails.client.ts line 3), the source directly imports the
pro implementation './pro/ClientSideRenderer.ts' which, combined with
package.json's exports map exposing ./node_package/lib/pro/*, unintentionally
exposes Pro modules to OSS consumers; either change these imports to point to
the OSS/non‑pro implementation (e.g. './ClientSideRenderer' or an internal
alias), or update the package.json exports to remove or alias all
./node_package/lib/pro/* entries so they are not published, or if intentional
keep exports but add clear documentation and access gating (feature flag or
private package) — implement one of these fixes and ensure the build output and
exports map are updated accordingly so consumers resolve to the intended
(non‑pro) implementation.

Comment on lines +3 to +5
import { renderOrHydrateComponent, hydrateStore } from './pro/ClientSideRenderer.ts';
import * as ComponentRegistry from './pro/ComponentRegistry.ts';
import * as StoreRegistry from './pro/StoreRegistry.ts';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Client now depends on Pro modules — verify packaging/licensing and gating.

Ensure OSS distribution doesn’t inadvertently ship proprietary code unguarded, and that Pro-only hydration gating remains in effect.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "License of node_package:" 
jq -r '.license' node_package/package.json

echo -e "\nExports mapping (look for client/node entries referencing pro paths):"
jq -r '.exports | to_entries[] | "\(.key): \(.value)"' node_package/package.json

echo -e "\nCheck gating in ClientSideRenderer for force-load & loading-state hydration:"
rg -nP -C2 "document\\.readyState\\s*===\\s*['\"]loading['\"]" node_package/src/pro/ClientSideRenderer.ts
rg -nP -C2 "\\brorPro\\b" node_package/src/pro/ClientSideRenderer.ts

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo -e "\nTop-level entries:"
ls -la | sed -n '1,200p' || true

echo -e "\nCheck for node_package directory:"
if [ -d node_package ]; then echo "FOUND node_package"; else echo "node_package not found"; fi

echo -e "\nFind package.json files (depth <= 6):"
find . -maxdepth 6 -type f -name package.json -print || true

echo -e "\nFor each package.json found, print path, name, license and exports (if any):"
while IFS= read -r p; do
  echo "---- $p ----"
  jq -r '["name: " + (.name // "N/A"), "license: " + (.license // "N/A"), "exports: " + ( (.exports|tojson) // "null")] | .[]' "$p" || true
done < <(find . -maxdepth 6 -type f -name package.json -print)

echo -e "\nSearch repository for references to pro/ paths or the target filenames:"
rg -n --hidden -S "from ['\"][./]*pro/|/pro/ClientSideRenderer|ReactOnRails.client.ts|ClientSideRenderer.ts|ComponentRegistry.ts|StoreRegistry.ts" || true

echo -e "\nShow head (first 60 lines) of any matched files for quick inspection:"
rg -n --hidden -S "from ['\"][./]*pro/|ReactOnRails.client.ts|ClientSideRenderer.ts|ComponentRegistry.ts|StoreRegistry.ts" -o --no-ignore -l 2>/dev/null | while read -r f; do
  echo "---- FILE: $f ----"
  sed -n '1,60p' "$f" || true
done

echo -e "\nSearch for gating / pro-license guard keywords:"
rg -n --hidden -S "pro-only|isPro|hasPro|PRO_LICENSE|process.env.PRO|process.env.PRO_ONLY|proGate|isEnterprise" || true

Length of output: 35735


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo
echo "ls -la node_package (if exists):"
ls -la node_package || true
echo

if [ -f node_package/package.json ]; then
  echo "node_package/package.json -> parsing name, license, exports (if present):"
  jq -r '["name: "+(.name//"N/A"), "license: "+(.license//"N/A"), "exports: "+( (.exports//null) | tojson )] | .[]' node_package/package.json || true
  echo
  echo "Full exports (raw):"
  jq -S '.exports' node_package/package.json || true
else
  echo "node_package/package.json not found"
fi

echo
echo "Find any package.json under node_package:"
find node_package -maxdepth 6 -type f -name package.json -print || true
echo

echo "Show top of node_package/src/ReactOnRails.client.ts if present:"
if [ -f node_package/src/ReactOnRails.client.ts ]; then
  sed -n '1,160p' node_package/src/ReactOnRails.client.ts || true
else
  echo "node_package/src/ReactOnRails.client.ts not found"
fi
echo

echo "Show top of node_package/src/pro/ClientSideRenderer.ts if present:"
if [ -f node_package/src/pro/ClientSideRenderer.ts ]; then
  sed -n '1,300p' node_package/src/pro/ClientSideRenderer.ts || true
else
  echo "node_package/src/pro/ClientSideRenderer.ts not found"
fi
echo

echo "Search imports referencing pro/ anywhere under node_package:"
rg -n --hidden -S "from\s+['\"][./]*pro/|['\"]/pro/" node_package || true
echo

echo "Search for hydration/gating keywords under node_package/src:"
rg -n --hidden -S "hydrateStore|renderOrHydrate|hydrate\(|document\.readyState|['\"]loading['\"]|force-?load|isPro|PRO_LICENSE|PRO_ONLY|process\.env\.PRO|isEnterprise|pro-only|hasPro|proGate" node_package/src || true
echo

echo "Print any matches of files that import pro modules (list files):"
rg -n --hidden -S "from\s+['\"][./]*pro/" node_package -o --no-ignore -l 2>/dev/null || true
echo

Length of output: 20293


Client imports Pro modules — stop shipping Pro code and confirm gating

  • Evidence: node_package/src/ReactOnRails.client.ts imports ./pro/ClientSideRenderer.ts, ./pro/ComponentRegistry.ts, ./pro/StoreRegistry.ts (top of file). node_package/src/pro/ClientSideRenderer.ts contains a Pro-only license header.
  • Action required: Do not publish the pro/ files with the OSS bundle — either remove/replace these imports from the OSS client entry, exclude node_package/src/pro from packaging/exports, or split Pro into a separate package. Also verify the package/build exports (packaging config or package.json used for publishing) will not include pro/ and confirm runtime gating still relies on railsContext.rorPro for Pro-only features (immediate_hydration). Provide the fix or packaging config that ensures Pro code is never shipped in the OSS distribution.
🤖 Prompt for AI Agents
In node_package/src/ReactOnRails.client.ts around lines 3 to 5, the OSS client
entry currently imports files from ./pro which contain Pro-only code and license
headers; remove these static imports and ensure Pro code is not shipped by
either 1) replacing the imports with OSS implementations or guarded dynamic
imports that only load ./pro/* when railsContext.rorPro is true at runtime, or
2) exclude node_package/src/pro from the published package by updating
package.json (files/exports) and build config so pro/ is not included in the npm
bundle; also verify runtime gating still uses railsContext.rorPro for features
like immediate_hydration and confirm packaging changes prevent any ./pro modules
from being exported or published.

Comment on lines +9 to 27
"react-server": "./node_package/lib/pro/ReactOnRailsRSC.js",
"node": "./node_package/lib/ReactOnRails.node.js",
"default": "./node_package/lib/ReactOnRails.full.js"
},
"./client": "./node_package/lib/ReactOnRails.client.js",
"./registerServerComponent/client": "./node_package/lib/registerServerComponent/client.js",
"./registerServerComponent/client": "./node_package/lib/pro/registerServerComponent/client.js",
"./registerServerComponent/server": {
"react-server": "./node_package/lib/registerServerComponent/server.rsc.js",
"default": "./node_package/lib/registerServerComponent/server.js"
"react-server": "./node_package/lib/pro/registerServerComponent/server.rsc.js",
"default": "./node_package/lib/pro/registerServerComponent/server.js"
},
"./wrapServerComponentRenderer/client": "./node_package/lib/wrapServerComponentRenderer/client.js",
"./wrapServerComponentRenderer/client": "./node_package/lib/pro/wrapServerComponentRenderer/client.js",
"./wrapServerComponentRenderer/server": {
"react-server": "./node_package/lib/wrapServerComponentRenderer/server.rsc.js",
"default": "./node_package/lib/wrapServerComponentRenderer/server.js"
"react-server": "./node_package/lib/pro/wrapServerComponentRenderer/server.rsc.js",
"default": "./node_package/lib/pro/wrapServerComponentRenderer/server.js"
},
"./RSCRoute": "./node_package/lib/RSCRoute.js",
"./RSCProvider": "./node_package/lib/RSCProvider.js",
"./ServerComponentFetchError": "./node_package/lib/ServerComponentFetchError.js"
"./RSCRoute": "./node_package/lib/pro/RSCRoute.js",
"./RSCProvider": "./node_package/lib/pro/RSCProvider.js",
"./ServerComponentFetchError": "./node_package/lib/pro/ServerComponentFetchError.js"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Exports moved under pro/: verify consumers and types.

The condition exports look right. Please:

  • Run publint/ATTW to validate the map.
  • Confirm TypeScript declaration resolution for the new pro paths (d.ts emitted under lib/pro). If missing, add a "types" field or conditional "types" export.

You can use this script:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Validate export map
npx -y publint

# Check types presence for pro entries
fd -t f -e d.ts 'node_package/lib/pro' -x echo '{}'

# Sanity: show export targets
jq '.exports' package.json

Length of output: 1638


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Quick package export + types verification ==="

if [ ! -f package.json ]; then
  echo "ERR: package.json not found at repo root"
  exit 1
fi

echo
echo "1) package.json 'types' field:"
jq '.types // "null"' package.json || true

echo
echo "2) package.json exports (top-level):"
jq '.exports' package.json || true

echo
echo "3) Check for node_package/lib/pro directory:"
if [ ! -d node_package/lib/pro ]; then
  echo "MISSING_DIR: node_package/lib/pro not found"
  echo "Listing any node_package dirs under repo:"
  find . -type d -name 'node_package' -print || true
  exit 2
fi

echo "node_package/lib/pro exists. Listing files (showing up to 200 entries):"
find node_package/lib/pro -maxdepth 4 -type f -print | sed -n '1,200p' || true

echo
echo "4) Verify .d.ts presence for export targets under node_package/lib/pro"
python3 - <<'PY'
import json, os, sys

with open('package.json') as f:
    pj = json.load(f)

exports = pj.get('exports', {})
types_field = pj.get('types', None)
print("types_field_present:", bool(types_field))

# collect all string targets from exports recursively
targets = []
def walk(obj):
    if isinstance(obj, str):
        targets.append(obj)
    elif isinstance(obj, dict):
        for v in obj.values():
            walk(v)
    elif isinstance(obj, list):
        for v in obj:
            walk(v)

walk(exports)

pro_targets = [t for t in targets if 'node_package/lib/pro' in t]
print("pro_targets_count:", len(pro_targets))
if not pro_targets:
    print("NOTE: no export targets reference node_package/lib/pro")
    sys.exit(0)

missing = []
for t in pro_targets:
    p = t[2:] if t.startswith('./') else t
    # Expected d.ts: replace .js with .d.ts
    if p.endswith('.js'):
        dts = p[:-3] + '.d.ts'
    else:
        dts = p + '.d.ts'
    exists = os.path.exists(dts)
    print(f"{dts}: {'FOUND' if exists else 'MISSING'}")
    if not exists:
        missing.append(dts)

if missing:
    print("MISSING_COUNT:", len(missing))
    for m in missing[:100]:
        print("MISSING:", m)
    sys.exit(3)
else:
    print("All expected .d.ts files for pro export targets are present.")
PY

echo
echo "Done."

Length of output: 1350


Exports point to missing pro build outputs and missing types — fix required

  • package.json exports (exports block — File: package.json, lines ~9–27) reference ./node_package/lib/pro/* but node_package/lib/pro is not present in the repo; either add the built outputs (JS + .d.ts) under node_package/lib/pro or update the exports to existing build paths.
  • package.json has no top-level "types" field; ensure TypeScript declarations for the pro entries are emitted and either add a top-level "types" or conditional "types" exports so consumers resolve .d.ts files.
  • publint validation couldn't complete earlier (publint failed during yarn pack with "/bin/sh: 1: nps: not found"); run yarn install (or ensure CI/devDeps include nps) and re-run publint to validate the export map after fixing the exports/build outputs.
🤖 Prompt for AI Agents
In package.json around lines 9 to 27, the exports map points to non-existent
./node_package/lib/pro/* outputs and there is no top-level types declaration;
fix by either (A) producing the missing pro build artifacts (JS + .d.ts) into
node_package/lib/pro and committing them, or (B) updating the exports map to
reference the actual built paths that exist in node_package/lib (remove or
replace "pro" entries), and add a top-level "types" field (or conditional
"types" exports) that points to the corresponding .d.ts files for the package
entrypoints; finally ensure publint can run by adding nps to devDependencies or
running yarn install to get nps, then re-run publint/yarn pack to validate the
export map.

Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791: Restrict usage of pro features to RORP licensed users

Thank you for this PR that implements licensing restrictions for React on Rails Pro features. I've conducted a thorough review and identified several important considerations.

🎯 Summary

This PR successfully separates Pro features into dedicated directories, but introduces potential breaking changes and has gaps in licensing enforcement and test coverage that need attention.

✅ Strengths

  • Clean separation: Pro features are well-isolated in lib/react_on_rails/pro_features/ and node_package/src/pro/
  • Consistent organization: File structure and namespace organization is logical
  • Proper license headers: Pro files correctly include license notices

🚨 Critical Issues

1. Breaking Changes for Existing Users

Moving core files like ComponentRegistry, StoreRegistry, and ClientSideRenderer to the pro/ directory will break any code that imports these modules directly. This is a breaking change that needs:

  • Clear documentation in CHANGELOG.md
  • Migration guide for affected users
  • Consideration for a major version bump

2. HTML String Modification Safety

In lib/react_on_rails/pro_features/helper.rb:31,46, using gsub! to modify HTML strings in-place is risky:

# Current (unsafe):
component_specification_tag.gsub!(/>\z/, ' data-immediate-hydration="true">')

# Recommended:
component_specification_tag = component_specification_tag.gsub(/>\z/, ' data-immediate-hydration="true">')

3. Insufficient Test Coverage

  • Tests mock react_on_rails_pro_licence_valid? to always return true (spec/dummy/spec/helpers/react_on_rails_helper_spec.rb:27)
  • No tests validate behavior when Pro features are requested without a valid license
  • Missing tests for the actual licensing enforcement logic

⚠️ Important Concerns

1. Silent Failures

When immediate_hydration is requested without a Pro license, the feature silently fails. Users might not realize why their components aren't hydrating immediately. Consider:

  • Adding console warnings when Pro features are requested but unavailable
  • Documenting this behavior clearly

2. Architecture Coupling

Core functionality still depends on Pro modules (e.g., ReactOnRails.client.ts imports from ./pro/). This creates a dependency where "core" features require Pro code to be present.

3. Package.json Exports

The package.json exposes Pro features in public exports, which could confuse users about what requires a license.

🔧 Actionable Recommendations

  1. Add comprehensive tests for license enforcement:
context "when Pro license is not valid" do
  before { allow(ReactOnRails::Utils).to receive(:react_on_rails_pro_licence_valid?).and_return(false) }
  
  it "ignores immediate_hydration option" do
    # Verify immediate hydration is not applied
  end
  
  it "displays warning badge" do
    # Verify warning is shown to user
  end
end
  1. Fix the HTML modification pattern to avoid in-place string mutations

  2. Add deprecation warnings for moved modules with clear migration paths

  3. Update documentation:

    • Add breaking changes to CHANGELOG.md
    • Document which features require Pro license
    • Provide migration guide for import path changes
  4. Consider performance optimization: Cache the warning badge HTML instead of generating it on every request

📝 Missing PR Checklist Items

Based on the PR template, these items still need attention:

  • Add/update tests to cover license enforcement
  • Update documentation for breaking changes
  • Update CHANGELOG file with breaking changes and Pro feature restrictions

💭 Overall Assessment

The architectural direction is sound and the separation of Pro features is well-executed. However, the breaking changes and gaps in testing/documentation need to be addressed before merging. This change warrants careful consideration of the upgrade path for existing users.

Please address the critical issues, especially around breaking changes and test coverage, before proceeding with the merge.

@AbanoubGhadban AbanoubGhadban changed the title Restrict usage of pro features to RORP licenced users Move RSC features into prop directory and update licence to make them part of RORP licence Sep 18, 2025
Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791: Restrict usage of pro features to RORP licensed users

🎯 Summary

This PR successfully refactors the React on Rails codebase to properly separate Pro features from the core MIT-licensed functionality. The implementation creates a clear boundary between open-source and proprietary code, centralizing license validation and Pro feature gating.

✅ Strengths

  1. Clean Separation of Concerns: The migration of Pro features to dedicated pro/ directories in both Ruby (lib/react_on_rails/pro_features/) and JavaScript (node_package/src/pro/) creates a clear architectural boundary.

  2. License Clarity: The updated LICENSE.md clearly distinguishes between MIT-licensed core and Pro-licensed directories, which is excellent for legal compliance.

  3. Consistent Licensing Headers: All Pro files now include proper copyright headers, which is crucial for protecting proprietary code.

  4. Minimal Breaking Changes: The public API remains unchanged for core users, with package exports properly routing through the Pro layer.

🔍 Areas for Improvement

1. Security Consideration - License Validation 🔒

The current implementation relies on ReactOnRails::Utils.react_on_rails_pro_licence_valid? for server-side validation. Consider:

  • Adding additional validation layers to prevent client-side bypassing
  • Implementing secure license key validation with server-side verification
  • Consider obfuscation or compilation of Pro JavaScript code to prevent tampering

2. Performance Impact

The introduction of the Pro layer adds an indirection for imports. While minimal, consider:

  • Measuring any performance impact on module loading
  • Ensuring tree-shaking still works effectively for users not using Pro features
  • Consider lazy loading Pro modules only when needed

3. Test Coverage 🧪

The test files have been updated to use new import paths, but consider:

  • Adding specific tests for license validation logic
  • Testing the behavior when Pro features are attempted without a valid license
  • Adding integration tests for the Pro warning badge display

4. Documentation Gaps 📚

  • The PR body template sections are not filled out (summary, checklist, changelog)
  • Consider adding documentation on how Pro license validation works
  • Migration guide for existing Pro users might be helpful

5. Code Organization Suggestions 🏗️

In lib/react_on_rails/pro_features/helper.rb:

  • The HTML for the warning badge is inline. Consider extracting to a template or constant for better maintainability
  • The warning message is duplicated between constant and method - consider DRY principle

In the JavaScript Pro modules:

  • Some imports still reference parent directories (../types/index.ts). Consider if these shared types should be in a common location

🐛 Potential Issues

  1. Missing REACT-ON-RAILS-PRO-LICENSE.md: The LICENSE.md references this file, but it's not included in the PR. This file should be added or the reference should be updated.

  2. Incomplete PR Description: The PR template checklist items are not marked, and the summary needs to be filled out with actual implementation details.

  3. Badge Display Logic: The Pro warning badge uses fixed positioning which might conflict with other fixed elements on the page. Consider making this configurable or using a less intrusive notification method.

💡 Recommendations

  1. Add License File: Include the REACT-ON-RAILS-PRO-LICENSE.md file referenced in LICENSE.md

  2. Complete PR Documentation: Fill out the PR template with:

    • Detailed summary of changes
    • Mark completed checklist items
    • Add CHANGELOG entry
  3. Consider Feature Flags: Instead of just license validation, consider implementing feature flags for more granular control over Pro features

  4. Add Telemetry: Consider adding (with user consent) telemetry to track Pro feature usage for better product insights

  5. Improve Error Messages: When Pro features are attempted without license, provide clear actionable messages with links to upgrade

✨ Overall Assessment

This is a well-structured refactor that successfully separates Pro features from the core codebase. The implementation is clean and maintains backward compatibility. With the suggested improvements, particularly around security, documentation, and the missing license file, this will provide a robust foundation for the Pro/Core split.

The code quality is good, following existing patterns and conventions. The approach to immediate hydration feature gating is elegant and non-intrusive for core users.

Recommendation: Address the missing license file and complete the PR documentation before merging. The other suggestions can be considered for follow-up improvements.

Copy link

claude bot commented Sep 18, 2025

Code Review for PR #1791

Thank you for this well-structured PR that moves RSC features into a dedicated Pro directory and updates licensing. Here's my comprehensive review:

✅ Strengths

  1. Clear separation of concerns: The Pro features are now cleanly separated into dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/), making the licensing boundaries explicit.

  2. Consistent licensing headers: All Pro files include proper copyright notices and license references.

  3. Backward compatibility maintained: Public API remains unchanged - exports are properly routed through the Pro layer.

  4. Good refactoring pattern: Helper methods extracted to ProFeatures::Helper module with clear naming (apply_immediate_hydration_if_supported, apply_store_immediate_hydration_if_supported).

⚠️ Areas for Improvement

1. Test Coverage Gap

The PR lacks tests for the new ReactOnRails::ProFeatures::Helper module. Consider adding:

  • Unit tests for apply_immediate_hydration_if_supported
  • Unit tests for apply_store_immediate_hydration_if_supported
  • Tests verifying behavior when Pro license is/isn't valid
  • Tests for the warning badge generation

2. Security: HTML Injection Risk

In lib/react_on_rails/pro_features/helper.rb line 30:

component_specification_tag.gsub!('<script ', '<script data-immediate-hydration="true" ')

This direct string manipulation could be fragile. Consider using proper DOM manipulation or tag builders to avoid potential edge cases.

3. Documentation Updates Missing

  • The CHANGELOG.md hasn't been updated as indicated in the PR checklist
  • Consider adding migration notes for users who might be importing from the old paths
  • Update README or docs to clarify which features require Pro license

4. Performance Consideration

The support_pro_features? method is called multiple times during rendering. Consider memoizing the license check result at the request level to avoid redundant validations.

5. Code Duplication

Lines 27-37 and 41-53 in pro_features/helper.rb have similar patterns. Consider extracting a shared method to reduce duplication.

🔍 Minor Issues

  1. Incomplete PR Description: The summary section needs to be filled out with actual changes made.

  2. Type Safety: In TypeScript files, ensure all imported types from parent directories are properly validated.

  3. Directory Naming: Consider renaming 'pro' directory to 'pro_features' to match the Ruby side naming for consistency.

🎯 Recommendations

  1. Add comprehensive test coverage before merging
  2. Update all documentation as per checklist
  3. Consider adding integration tests that verify Pro features are properly gated
  4. Add a comment in package.json explaining why exports point to the pro directory

Overall, this is a solid refactoring that achieves the goal of separating Pro features. With the suggested improvements, particularly around testing and documentation, this will be ready to merge.


Review generated with assistance from Claude Code

Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791: Move RSC features into pro directory

Overall Assessment

This PR successfully reorganizes the codebase to clearly separate Pro features from the MIT-licensed Core features. The implementation is well-structured and follows good software engineering practices.

✅ Strengths

  1. Clear License Separation: The updated LICENSE.md clearly distinguishes between MIT-licensed Core and separately licensed Pro features, with explicit directory listings.

  2. Clean Module Organization: Moving Pro features to dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/) creates excellent separation of concerns.

  3. Proper License Headers: All Pro files include appropriate copyright headers referencing the React on Rails Pro License.

  4. Backward Compatibility: The refactoring maintains the existing public API by properly re-exporting from the Pro modules.

🔍 Code Quality Observations

  1. Helper Module Pattern: The extraction of Pro-specific helper methods into ReactOnRails::ProFeatures::Helper is clean and follows Ruby best practices with proper module inclusion.

  2. Conditional Feature Support: The support_pro_features? check ensures graceful degradation when Pro license is not available.

  3. User Feedback: The Pro warning badge implementation provides clear visual feedback when Pro features are attempted without a valid license.

⚠️ Areas for Consideration

  1. Test Coverage: While tests appear to be migrated, ensure all Pro features have comprehensive test coverage, especially around the license validation logic.

  2. Documentation: The PR description mentions updating documentation but doesn't show doc file changes. Ensure README and guides are updated to reflect the new structure.

  3. Import Path Changes: The migration changes import paths (e.g., from ./ComponentRegistry.ts to ./pro/ComponentRegistry.ts). Verify all dependent code is updated.

🔒 Security Considerations

  • License validation appears to be server-side which is good
  • No sensitive information or keys exposed in the diff
  • Proper separation prevents accidental exposure of Pro features

🎯 Suggestions

  1. Add REACT-ON-RAILS-PRO-LICENSE.md: The LICENSE.md references this file but it's not included in the PR. This should be added for completeness.

  2. Migration Guide: Consider adding a brief migration guide for existing Pro users about any changes they need to make.

  3. CI/CD Validation: Ensure CI validates that Pro features are properly gated behind license checks.

Performance Impact

No significant performance concerns identified. The module structure changes are compile/load-time only.

Conclusion

This is a well-executed refactoring that achieves its goal of clearly separating Pro features while maintaining backward compatibility. The code is clean, follows established patterns, and properly handles license validation. With the minor suggestions addressed, this PR is ready for merge.

Recommendation: ✅ Approve with minor suggestions

Copy link

claude bot commented Sep 19, 2025

Code Review for PR 1791: Move RSC features into pro directory

Summary

This PR successfully restructures the codebase to separate Pro features from the MIT-licensed core, moving React Server Components (RSC) and related features into dedicated pro directories. The implementation is well-organized and maintains backwards compatibility while properly enforcing licensing boundaries.

Strengths

  1. Clean Separation of Concerns - The creation of lib/react_on_rails/pro_features/ and node_package/src/pro/ directories clearly delineates Pro features from core functionality.
  2. Licensing Clarity - Updated LICENSE.md effectively communicates the dual licensing model with clear distinction between MIT and Pro licenses.
  3. Backwards Compatibility - The refactoring preserves the public API - imports and module exports are properly routed through the Pro layer without breaking changes for existing users.
  4. DRY Principle - The extraction of immediate hydration logic into dedicated helper methods reduces code duplication.

Areas for Improvement

1. Potential Bug in gsub! Usage

In lib/react_on_rails/pro_features/helper.rb, the string replacement only replaces the first occurrence and might not work if the script tag has different formatting. Consider using a more robust regex approach to handle various formatting cases.

2. Missing Error Handling

The Pro helper methods don't handle edge cases where the content_tag might return nil or where the DOM ID contains special characters that could break JavaScript. Add validation for DOM IDs to ensure they match valid identifier patterns.

3. Performance Consideration

The pro_warning_badge_if_needed method performs console output on every call when Pro features aren't licensed. This could spam logs in production. Consider rate limiting or showing the warning only once per session.

4. Test Coverage Gap

The test changes only update import paths but don't add tests for the new Pro module behavior, particularly around license validation and feature gating. Add tests for Pro feature availability with/without valid license, proper rendering of warning badges, and immediate hydration behavior.

5. Security Consideration

The immediate hydration scripts directly interpolate DOM IDs and store names into JavaScript without escaping. Use proper JavaScript escaping to prevent potential XSS vulnerabilities.

Checklist Items

  • Tests: While import paths are updated, new test coverage for Pro feature gating is missing
  • Documentation: No updates to documentation explaining the new Pro structure
  • CHANGELOG: No entry added for this significant architectural change

Overall Assessment

This is a well-executed refactoring that achieves its primary goal of separating Pro features. However, it needs additional attention to error handling, security, and test coverage before merging. The architectural decisions are sound, but the implementation details require refinement.

Recommendation: Address the security concerns and add comprehensive tests before merging. The documentation should also be updated to reflect the new structure.

Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791: Move RSC features into pro directory

✅ Overall Assessment

This PR successfully restructures the codebase to separate Pro features from the MIT-licensed core functionality. The approach is generally sound, but there are several areas that need attention before merging.

🔍 Key Observations

1. Code Quality & Structure

  • Clean separation: The Pro features are properly isolated in dedicated directories (lib/react_on_rails/pro_features/ and node_package/src/pro/)
  • Licensing clarity: LICENSE.md clearly distinguishes between MIT and Pro licensed code
  • ⚠️ Module organization: The approach uses Ruby modules and TypeScript imports effectively

2. Potential Issues

a. License Header Consistency

The Pro files have proper license headers, but ensure ALL files in the pro directories include the copyright notice. Some moved files might be missing the header.

b. Test Coverage Gap
  • The PR checklist indicates tests need to be added/updated, but I don't see corresponding test changes for the refactored Pro features
  • Tests still import from old paths (e.g., ComponentRegistry.test.js, StoreRegistry.test.js)
  • Consider adding tests to verify Pro features are properly gated when license is invalid
c. Documentation Updates Missing
  • The PR checklist shows documentation needs updating
  • No CHANGELOG.md entry as required by the checklist
  • Consider updating README or docs to explain the Pro/Core separation

3. Performance Considerations

  • ✅ The immediate hydration feature maintains its performance characteristics
  • ✅ No additional overhead introduced for non-Pro users
  • ⚠️ Consider lazy loading Pro modules on client-side to reduce bundle size for Core users

4. Security Concerns

  • ⚠️ Client-side validation only: The Pro feature check appears to rely on server-side support_pro_features? but client-side enforcement is unclear
  • 💡 Suggestion: Ensure Pro features fail gracefully on client if server validation is bypassed
  • ✅ The warning badge implementation is non-intrusive and informative

📋 Specific Recommendations

  1. Complete the PR checklist items:

    - [ ] Add/update test to cover these changes
    - [ ] Update documentation  
    - [ ] Update CHANGELOG file
  2. Fix test imports - Update test files to import from new Pro paths:

    // Instead of:
    import * as ComponentRegistry from '../src/ComponentRegistry.ts';
    // Use:
    import * as ComponentRegistry from '../src/pro/ComponentRegistry.ts';
  3. Add Pro feature tests - Create test cases for:

    • Pro features disabled when license invalid
    • Warning badge displays correctly
    • Immediate hydration skipped without Pro license
  4. Consider adding TypeScript types for Pro feature availability:

    interface ReactOnRailsConfig {
      isProLicensed: boolean;
      // ...
    }
  5. Document migration path - Add a migration guide for existing Pro users about the new structure

🐛 Minor Issues

  1. Typo in PR title: "licence" should be "license" (American spelling is more common in code)
  2. Missing REACT-ON-RAILS-PRO-LICENSE.md: The LICENSE.md references this file but it's not included in the PR

✨ Positive Aspects

  1. Excellent separation of concerns - Core users won't be affected by Pro code
  2. Clear licensing boundaries - Makes it obvious what requires a Pro license
  3. Maintains backward compatibility - Existing imports still work through the new structure
  4. Good use of Ruby modules - The ProFeatures::Helper module is well-designed

📊 Risk Assessment

  • Low risk for Core users (no functionality changes)
  • Medium risk for Pro users (need thorough testing of moved features)
  • Low security risk (assuming server-side validation remains intact)

✅ Recommendation

Approve with changes - The refactoring is well-executed, but the PR needs:

  1. Test updates and additions
  2. Documentation updates
  3. CHANGELOG entry
  4. Minor fixes mentioned above

Great work on the architectural improvement! The separation will make licensing clearer and maintenance easier going forward. 🚀

Copy link

claude bot commented Sep 19, 2025

Code Review: Moving RSC Features to Pro Directory

✅ Strengths

1. Clear Separation of Concerns

  • Excellent architectural decision to move Pro features into dedicated lib/react_on_rails/pro_features/ and node_package/src/pro/ directories
  • Clean licensing boundary with MIT for core and proprietary for Pro features

2. Improved Code Organization

  • Modular approach with ReactOnRails::Pro::Helper module inclusion
  • Better encapsulation of Pro-specific logic (immediate hydration, force loading)
  • Consistent naming patterns across Ruby and JS/TS implementations

3. Backward Compatibility

  • Maintains existing public API through export redirects in package.json
  • No breaking changes for existing core users

⚠️ Issues to Address

1. PR Documentation Incomplete

  • The PR description has unfilled template sections. Please provide:
    • Summary of changes and motivation
    • Confirmation of checklist items (tests, docs, CHANGELOG)
    • Any migration notes for existing Pro users

2. Missing CHANGELOG Entry

  • This is a significant architectural change that needs documentation in CHANGELOG.md

3. License File Reference

  • REACT-ON-RAILS-PRO-LICENSE.md is referenced but not included in the PR. This file should be added or the reference updated.

4. Test Coverage Gaps

  • New pro/ directory modules lack comprehensive unit tests
  • Missing integration tests for license validation in streaming features

🐛 Potential Bugs

1. XSS Protection Update (node_package/src/pro/ClientSideRenderer.ts)
Good fix using CSS.escape() for DOM queries with component IDs. Verify all similar queries are protected.

2. Mutable String Operations (lib/react_on_rails/pro/helper.rb:31,46)

component_specification_tag.gsub!("<script ", '<script data-immediate-hydration="true" ')

Using gsub! directly modifies the input string. Consider using non-mutating gsub to avoid side effects.

🔒 Security Considerations

1. License Validation

  • License check relies on ReactOnRails::Utils.react_on_rails_pro_licence_valid? - ensure this is secure and tamper-resistant
  • Consider server-side validation for critical features rather than client-side checks only

2. Pro Badge Injection

  • The HTML badge injection (pro/helper.rb:72-80) uses inline styles with high z-index (9999)
  • Consider using CSS classes instead for better CSP compatibility

⚡ Performance Suggestions

1. Module Loading

  • Pro features are always imported even when not licensed. Consider lazy loading Pro modules only when needed.

2. License Check Caching

  • support_pro_features? is called multiple times. Consider caching the result per request.

📝 Additional Recommendations

  1. Add package.oss.json Documentation: Explain the purpose of this OSS-specific package configuration

  2. Update knip.ts Ignore Patterns: Good addition of Pro feature exports to ignore list

  3. Consider Error Boundaries: For Pro features that fail license validation, implement graceful degradation

  4. Migration Guide: If existing Pro users need to update imports, provide clear migration instructions

✅ Testing Verification Needed

Please confirm:

  • All existing tests pass with these changes
  • New Pro features have adequate test coverage
  • License validation is tested in both Ruby and JS/TS
  • Integration tests cover the immediate_hydration feature

Overall, this is a well-structured refactoring that improves code organization and licensing clarity. The main concerns are around documentation completeness and ensuring robust test coverage for the new Pro module structure.

Copy link
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: 3

🧹 Nitpick comments (5)
package.oss.json (1)

17-17: Security concern: Pro directory should be excluded from OSS distribution.

The files field pattern ["node_package/lib", "!node_package/lib/pro"] attempts to exclude the Pro directory, but this might not work as expected. The second pattern with the negation might not properly exclude subdirectories in all npm versions.

Consider using a more explicit approach:

-  "files": ["node_package/lib", "!node_package/lib/pro"],
+  "files": [
+    "node_package/lib/**/*.js",
+    "node_package/lib/**/*.d.ts", 
+    "!node_package/lib/pro",
+    "!node_package/lib/pro/**"
+  ],

Alternatively, consider copying only the OSS files to a separate dist directory during the build process to ensure complete separation.

knip.ts (1)

26-34: Consider documenting the Pro API exports.

The ignore list now includes Pro feature exports that are consumed externally. These should be documented to clarify which APIs are part of the Pro offering.

Consider adding a comment block that documents the purpose of each exported Pro API:

       ignore: [
         'node_package/tests/emptyForTesting.js',
-        // Pro features exported for external consumption
+        // Pro features exported for external consumption:
+        // - streamServerRenderedReactComponent: Server-side streaming rendering utilities
+        // - ServerComponentFetchError: RSC error handling 
+        // - RSCRoute: React Server Components routing
         'node_package/src/pro/streamServerRenderedReactComponent.ts:transformRenderStreamChunksToResultObject',
node_package/tests/proLicenseValidation.test.js (2)

38-46: Test should be async for consistency.

While the test works, it should be marked as async and await the result for consistency with the other tests and to properly handle the Promise-based API.

-    it('should proceed normally when Pro license is detected', () => {
+    it('should proceed normally when Pro license is detected', async () => {
       getRailsContext.mockReturnValue({ rorPro: true });

       // Test that it doesn't warn when license is valid (no force-load elements present)
-      const result = hydrateForceLoadedStores();
+      const result = await hydrateForceLoadedStores();

       expect(consoleSpy).not.toHaveBeenCalled();
       expect(result).toBeDefined();
     });

25-59: Consider adding an integration test with actual DOM elements.

The current tests verify the license gating logic well, but don't test the actual hydration behavior when force-loaded stores are present in the DOM.

Add a test case that validates the full hydration flow:

it('should hydrate force-loaded stores when Pro license is valid and stores are present', async () => {
  getRailsContext.mockReturnValue({ rorPro: true });
  
  // Set up DOM with force-loaded store
  document.body.innerHTML = `
    <script type="application/json" 
            data-js-react-on-rails-store="TestStore" 
            data-force-load="true">
      {"initialState": "test"}
    </script>
  `;
  
  // Mock the store registry
  const mockHydrateStore = jest.fn().mockResolvedValue(undefined);
  jest.mock('../src/pro/ClientSideRenderer.ts', () => ({
    ...jest.requireActual('../src/pro/ClientSideRenderer.ts'),
    hydrateStore: mockHydrateStore
  }));
  
  await hydrateForceLoadedStores();
  
  expect(mockHydrateStore).toHaveBeenCalledWith(expect.any(Element));
  expect(consoleSpy).not.toHaveBeenCalled();
});
lib/react_on_rails/helper.rb (1)

651-652: Consider removing the immediate_hydration override logic.

Since the Pro helper methods already check for Pro support internally, the override on line 652 seems redundant. The Pro helper will return the unmodified tag when Pro features aren't supported.

Consider removing the redundant override:

       immediate_hydration_requested = render_options.immediate_hydration
-      render_options.set_option(:immediate_hydration, false) unless support_pro_features?

The Pro helper already handles this check, so this line adds unnecessary complexity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a6bd67 and 38d5408.

📒 Files selected for processing (8)
  • knip.ts (1 hunks)
  • lib/react_on_rails/helper.rb (3 hunks)
  • lib/react_on_rails/pro/helper.rb (1 hunks)
  • node_package/src/pro/ClientSideRenderer.ts (4 hunks)
  • node_package/tests/proLicenseValidation.test.js (1 hunks)
  • package.oss.json (1 hunks)
  • spec/dummy/client/app/startup/HelloWorldRehydratable.jsx (1 hunks)
  • spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for all JavaScript/TypeScript code

Files:

  • spec/dummy/client/app/startup/HelloWorldRehydratable.jsx
  • node_package/tests/proLicenseValidation.test.js
  • knip.ts
  • node_package/src/pro/ClientSideRenderer.ts
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Use RuboCop for all Ruby code

Files:

  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
lib/**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Place Ruby gem source under lib/ (e.g., lib/react_on_rails/*)

Files:

  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
node_package/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place NPM package client-side source under node_package/src

Files:

  • node_package/src/pro/ClientSideRenderer.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • spec/dummy/client/app/startup/HelloWorldRehydratable.jsx
  • node_package/tests/proLicenseValidation.test.js
  • lib/react_on_rails/pro/helper.rb
  • node_package/src/pro/ClientSideRenderer.ts
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • node_package/tests/proLicenseValidation.test.js
  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-09-16T02:48:43.027Z
Learnt from: CR
PR: shakacode/react_on_rails#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T02:48:43.027Z
Learning: Applies to {coverage,tmp,gen-examples,node_package/lib,node_modules,spec/dummy/node_modules,spec/dummy/tmp,spec/dummy/app/assets/webpack,spec/dummy/log}/** : Exclude generated/build/vendor directories from IDE indexing to avoid slowdowns

Applied to files:

  • knip.ts
📚 Learning: 2025-09-16T02:48:43.027Z
Learnt from: CR
PR: shakacode/react_on_rails#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T02:48:43.027Z
Learning: Applies to node_package/src/**/*.{ts,tsx,js,jsx} : Place NPM package client-side source under node_package/src

Applied to files:

  • knip.ts
📚 Learning: 2025-09-16T02:48:43.027Z
Learnt from: CR
PR: shakacode/react_on_rails#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T02:48:43.027Z
Learning: Applies to node_package/lib/** : Do not edit compiled output; TypeScript compiles to node_package/lib

Applied to files:

  • knip.ts
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • knip.ts
  • package.oss.json
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • lib/react_on_rails/helper.rb
  • package.oss.json
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • lib/react_on_rails/helper.rb
🧬 Code graph analysis (4)
node_package/tests/proLicenseValidation.test.js (2)
node_package/src/context.ts (1)
  • getRailsContext (14-32)
node_package/src/pro/ClientSideRenderer.ts (1)
  • hydrateForceLoadedStores (315-325)
lib/react_on_rails/pro/helper.rb (2)
lib/react_on_rails/react_component/render_options.rb (2)
  • immediate_hydration (98-100)
  • dom_id (48-56)
lib/react_on_rails/utils.rb (1)
  • react_on_rails_pro_licence_valid? (216-229)
node_package/src/pro/ClientSideRenderer.ts (1)
node_package/src/context.ts (1)
  • getRailsContext (14-32)
lib/react_on_rails/helper.rb (1)
lib/react_on_rails/pro/helper.rb (2)
  • apply_immediate_hydration_if_supported (27-39)
  • apply_store_immediate_hydration_if_supported (42-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: claude-review
🔇 Additional comments (12)
spec/dummy/client/app/startup/HelloWorldRehydratable.jsx (2)

45-47: Good security improvement with CSS.escape!

Using CSS.escape for dynamic ID values prevents CSS injection vulnerabilities and ensures selector reliability when component names contain special characters.


52-54: LGTM! Consistent escaping pattern applied.

The escaping here properly handles the dynamic component ID value, maintaining consistency with the previous selector update.

knip.ts (1)

8-19: LGTM! Clean separation of Pro modules.

The reorganization properly moves all RSC and Pro-specific features into the node_package/src/pro/ directory, establishing a clear boundary between OSS and Pro code.

node_package/src/pro/ClientSideRenderer.ts (4)

1-13: LGTM! Appropriate license header for Pro features.

The proprietary license header clearly indicates this is part of the Pro offering and not MIT-licensed.


80-82: Good security improvement with CSS.escape!

The addition of CSS.escape properly handles special characters in DOM IDs, preventing selector injection issues.


303-303: Consistent escaping pattern applied.

Good to see the same CSS.escape pattern used for store name selectors as well.


315-325: Well-implemented Pro license gating for force loading.

The license check is properly implemented with appropriate warning message and graceful fallback. This aligns well with the test coverage in proLicenseValidation.test.js.

lib/react_on_rails/pro/helper.rb (1)

1-15: LGTM! Consistent Pro licensing header.

The Ruby Pro helper module has the appropriate proprietary license header matching the TypeScript Pro modules.

lib/react_on_rails/helper.rb (3)

14-14: LGTM! Clean separation of Pro features.

The inclusion of the Pro helper module properly delegates Pro-specific functionality while keeping the core helper clean.

Also applies to: 19-19


664-666: Good refactoring to use Pro helper methods.

The delegation to apply_immediate_hydration_if_supported properly encapsulates the Pro feature logic.


684-686: Consistent Pro feature delegation for stores.

The store hydration follows the same pattern as component hydration, maintaining consistency.

package.oss.json (1)

1-97: Verify OSS distribution strategy and export paths

  • Ensure build produces node_package/lib/ReactOnRails.full.js, ReactOnRails.node.js, ReactOnRails.client.js before publishing — package.oss.json.exports references these but the repo currently lacks the built files.
  • Clarify publish flow: package.json contains pro exports under node_package/lib/pro/* while package.oss.json omits them — state which manifest CI/publish uses and how package.oss.json is applied (e.g., copied/overwritten during release) so pro files are never published.
  • tsconfig.json couldn't be parsed with jq; confirm compilerOptions.outDir targets node_package/lib. Add/update README/CONTRIBUTING/RELEASE docs describing the dual-package workflow, prepack/publish steps, and how the "files" exclusion prevents node_package/lib/pro from being published.

LICENSE.md Outdated

This license applies to all files within this repository, with the exception of the code located in the following directories, which are licensed separately under the React on Rails Pro License:

- `lib/react_on_rails/pro_features/`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- `lib/react_on_rails/pro_features/`
- `lib/react_on_rails/pro/`

Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791

Summary

This PR moves React Server Components (RSC) and related Pro features into a dedicated pro directory and updates licensing to properly distinguish between MIT-licensed core features and Pro-licensed capabilities.

✅ Strengths

Code Quality & Architecture

  • Clean separation of concerns: Excellent job moving all Pro features into dedicated lib/react_on_rails/pro/ and node_package/src/pro/ directories
  • Proper license headers: All Pro modules now include appropriate copyright headers indicating they are not MIT-licensed
  • Backward compatibility maintained: Import paths are properly redirected while keeping public APIs unchanged

Security Improvements

  • DOM selector hardening: Important security fix using CSS.escape() for dynamic ID selectors prevents potential XSS vulnerabilities
  • This addresses selector injection risks when IDs contain special characters

Licensing & Documentation

  • Clear license boundaries: LICENSE.md clearly specifies which directories contain Pro-licensed code
  • OSS package manifest: Addition of package.oss.json helps with distribution management

⚠️ Areas for Improvement

1. PR Documentation

  • The PR description template is not filled out
  • Missing specific details about what functionality moved to Pro
  • Checklist items unchecked

2. Test Coverage

  • Good addition of proLicenseValidation.test.js for license checks
  • However, should verify all Pro features properly check for license before executing
  • Consider adding integration tests that verify core features work without Pro license

3. Error Messaging

  • When Pro features are used without a license, ensure error messages clearly explain:
    • What feature requires Pro
    • How to obtain a Pro license
    • Alternative approaches with core features (if any)

🔍 Potential Issues

Import Path Updates

All imports have been updated to point to the pro directory. Since ComponentRegistry, StoreRegistry, and ClientSideRenderer are now in Pro, are these still needed for basic React on Rails functionality? If yes, consider if there should be a minimal core implementation.

License Validation

The Pro features check railsContext.rorPro - ensure this is:

  1. Properly set from the Ruby side
  2. Cannot be easily bypassed client-side
  3. Validated server-side for SSR features

📋 Recommendations

  1. Complete PR template: Fill out the description with specific changes and rationale
  2. Update CHANGELOG.md: Document this as a breaking change if any public APIs moved
  3. Migration guide: If this affects existing users, provide clear migration instructions
  4. Documentation: Update README to clarify which features require Pro license
  5. Consider gradual rollout: Ensure existing Pro customers have smooth transition

Performance Considerations

  • No performance regressions expected as this is primarily a reorganization
  • The additional license checks should have negligible overhead

Overall Assessment

This is a well-executed architectural change that properly separates Pro features while maintaining backward compatibility. The security improvements with CSS.escape() are particularly valuable. Once the documentation and testing suggestions are addressed, this PR will be ready to merge.

The code quality is good, and the licensing structure is now much clearer. Great work on maintaining clean separation without breaking existing functionality!

Copy link
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: 0

Caution

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

⚠️ Outside diff range comments (3)
node_package/src/pro/CallbackRegistry.ts (2)

50-75: Potential TS type error and runtime guard needed for timeoutId; also improve timeout validation.

  • let timeoutId: NodeJS.Timeout; can error in browser builds and is used before assignment in clearTimeout(timeoutId).
  • Treat only positive numbers as valid timeouts and guard clearing.

Apply this diff:

-    let timeoutId: NodeJS.Timeout;
+    let timeoutId: ReturnType<typeof setTimeout> | undefined;
@@
-      const registryTimeout = getRailsContext()?.componentRegistryTimeout;
-      if (!registryTimeout) return;
-
-      timeoutId = setTimeout(triggerTimeout, registryTimeout);
+      const registryTimeout = getRailsContext()?.componentRegistryTimeout;
+      if (registryTimeout == null || registryTimeout <= 0) return;
+      timeoutId = setTimeout(triggerTimeout, registryTimeout);
@@
-      this.waitingPromises.clear();
-      this.timedout = false;
-      clearTimeout(timeoutId);
+      this.waitingPromises.clear();
+      this.timedout = false;
+      if (timeoutId !== undefined) {
+        clearTimeout(timeoutId);
+        timeoutId = undefined;
+      }

113-135: Race: promise may never resolve if set(name, item) happens between NotFound and waitingPromises.set.

There’s a window after the get(name) throws and before we store the resolver. A concurrent set will not resolve the promise leading to a hang.

Apply this diff:

       let promiseResolve: (value: T | PromiseLike<T>) => void = () => {};
       let promiseReject: (reason?: unknown) => void = () => {};
       const promise = new Promise<T>((resolve, reject) => {
         promiseResolve = resolve;
         promiseReject = reject;
       });
       this.waitingPromises.set(name, { resolve: promiseResolve, reject: promiseReject, promise });
-      return promise;
+      // Re-check in case the item was registered during the window above.
+      const already = this.registeredItems.get(name);
+      if (already) {
+        promiseResolve(already);
+        this.waitingPromises.delete(name);
+      }
+      return promise;
node_package/src/pro/ReactOnRailsRSC.ts (1)

36-76: Renderer caching may couple requests to the first manifest.

A single module‑level promise (serverRendererPromise / clientRendererPromise) ignores per‑request reactClientManifestFileName and can mix manifests in multi‑bundle or per‑tenant setups — key cached renderers by manifest filename.

-let serverRendererPromise: Promise<ReturnType<typeof buildServerRenderer>> | undefined;
+const serverRendererPromises = new Map<string, Promise<ReturnType<typeof buildServerRenderer>>>();

@@
-    if (!serverRendererPromise) {
-      serverRendererPromise = loadJsonFile<BundleManifest>(reactClientManifestFileName)
-        .then((reactClientManifest) => buildServerRenderer(reactClientManifest))
-        .catch((err: unknown) => {
-          serverRendererPromise = undefined;
-          throw err;
-        });
-    }
-
-    const { renderToPipeableStream } = await serverRendererPromise;
+    if (!serverRendererPromises.has(reactClientManifestFileName)) {
+      serverRendererPromises.set(
+        reactClientManifestFileName,
+        loadJsonFile<BundleManifest>(reactClientManifestFileName)
+          .then((m) => buildServerRenderer(m))
+          .catch((err: unknown) => {
+            serverRendererPromises.delete(reactClientManifestFileName);
+            throw err;
+          }),
+      );
+    }
+    const { renderToPipeableStream } = await serverRendererPromises.get(reactClientManifestFileName)!;

Also update node_package/src/pro/getReactServerComponent.server.ts (clientRendererPromise) and any other module‑level renderer promises to use a Map keyed by the relevant manifest filename(s).

🧹 Nitpick comments (6)
node_package/src/pro/CallbackRegistry.ts (2)

104-107: Consider clearing/rejecting waiters in clear() to avoid dangling promises.

Currently clear() leaves waitingPromises unresolved. Either reject them or document that clear() only affects registered/notUsed items.

Example change:

   clear(): void {
     this.registeredItems.clear();
     this.notUsedItems.clear();
+    this.waitingPromises.forEach((w, itemName) =>
+      w.reject(this.createNotFoundError(itemName))
+    );
+    this.waitingPromises.clear();
+    this.timedout = false;
   }

23-27: Loosen reject parameter type for consistency with Promise.reject.

reject in WaitingPromiseInfo is typed as (error: Error) => void but native Promise reject accepts any. Using unknown is safer and avoids needless casts.

Apply this diff:

 type WaitingPromiseInfo<T> = {
   resolve: ItemRegistrationCallback<T>;
-  reject: (error: Error) => void;
+  reject: (error: unknown) => void;
   promise: Promise<T>;
 };
node_package/src/pro/getReactServerComponent.client.ts (1)

75-85: Encode componentName in URL.

Component names with non‑URL‑safe chars will break the path.

Apply:

-    const fetchUrl = `/${strippedUrlPath}/${componentName}?${encodedParams}`;
+    const encodedComponentName = encodeURIComponent(componentName);
+    const fetchUrl = `/${strippedUrlPath}/${encodedComponentName}?${encodedParams}`;
node_package/src/pro/ClientSideRenderer.ts (3)

80-82: Guard CSS.escape for older environments (e.g., some test runners).

CSS.escape may be undefined in certain non‑browser/test contexts. Use a small fallback helper.

Apply this diff:

-        ? document.querySelector(`[data-dom-id="${CSS.escape(domId)}"]`)
+        ? document.querySelector(`[data-dom-id="${cssEscape(domId)}"]`)

Add this helper near the top (after imports):

// Lightweight CSS.escape fallback for older environments (e.g., some test runners)
function cssEscape(value: string): string {
  try {
    if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(value);
  } catch {}
  // Minimal fallback for use inside double-quoted attribute selectors
  return value.replace(/["\\]/g, '\\$&');
}

85-87: Wrap JSON.parse of data-store-dependencies to avoid hard failure on bad markup.

A malformed attribute will throw during construction and abort rendering.

Apply this diff:

-    const storeDependencies = el.getAttribute('data-store-dependencies');
-    const storeDependenciesArray = storeDependencies ? (JSON.parse(storeDependencies) as string[]) : [];
+    let storeDependenciesArray: string[] = [];
+    try {
+      const storeDependencies = el.getAttribute('data-store-dependencies');
+      storeDependenciesArray = storeDependencies ? (JSON.parse(storeDependencies) as string[]) : [];
+    } catch (err) {
+      console.warn('Invalid JSON in data-store-dependencies:', err);
+    }

303-303: Also use CSS.escape fallback for store selector.

Mirror the dom-id fix for store names.

Apply this diff:

-        ? document.querySelector(`[${REACT_ON_RAILS_STORE_ATTRIBUTE}="${CSS.escape(storeNameOrElement)}"]`)
+        ? document.querySelector(
+            `[${REACT_ON_RAILS_STORE_ATTRIBUTE}="${cssEscape(storeNameOrElement)}"]`,
+          )

(Uses the same cssEscape helper suggested above.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38d5408 and 58212db.

📒 Files selected for processing (23)
  • lib/react_on_rails/pro/helper.rb (1 hunks)
  • node_package/src/pro/CallbackRegistry.ts (1 hunks)
  • node_package/src/pro/ClientSideRenderer.ts (4 hunks)
  • node_package/src/pro/ComponentRegistry.ts (1 hunks)
  • node_package/src/pro/PostSSRHookTracker.ts (1 hunks)
  • node_package/src/pro/RSCProvider.tsx (1 hunks)
  • node_package/src/pro/RSCRequestTracker.ts (1 hunks)
  • node_package/src/pro/RSCRoute.tsx (1 hunks)
  • node_package/src/pro/ReactOnRailsRSC.ts (3 hunks)
  • node_package/src/pro/ServerComponentFetchError.ts (1 hunks)
  • node_package/src/pro/StoreRegistry.ts (1 hunks)
  • node_package/src/pro/getReactServerComponent.client.ts (1 hunks)
  • node_package/src/pro/getReactServerComponent.server.ts (1 hunks)
  • node_package/src/pro/injectRSCPayload.ts (1 hunks)
  • node_package/src/pro/registerServerComponent/client.tsx (1 hunks)
  • node_package/src/pro/registerServerComponent/server.rsc.ts (1 hunks)
  • node_package/src/pro/registerServerComponent/server.tsx (1 hunks)
  • node_package/src/pro/streamServerRenderedReactComponent.ts (2 hunks)
  • node_package/src/pro/transformRSCNodeStream.ts (1 hunks)
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts (1 hunks)
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx (1 hunks)
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx (1 hunks)
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • node_package/src/pro/transformRSCNodeStream.ts
🚧 Files skipped from review as they are similar to previous changes (18)
  • node_package/src/pro/getReactServerComponent.server.ts
  • node_package/src/pro/wrapServerComponentRenderer/server.tsx
  • node_package/src/pro/transformRSCStreamAndReplayConsoleLogs.ts
  • node_package/src/pro/registerServerComponent/server.tsx
  • node_package/src/pro/wrapServerComponentRenderer/server.rsc.tsx
  • node_package/src/pro/injectRSCPayload.ts
  • node_package/src/pro/registerServerComponent/client.tsx
  • node_package/src/pro/RSCRoute.tsx
  • node_package/src/pro/PostSSRHookTracker.ts
  • node_package/src/pro/ComponentRegistry.ts
  • node_package/src/pro/RSCProvider.tsx
  • node_package/src/pro/streamServerRenderedReactComponent.ts
  • node_package/src/pro/ServerComponentFetchError.ts
  • node_package/src/pro/wrapServerComponentRenderer/client.tsx
  • node_package/src/pro/RSCRequestTracker.ts
  • node_package/src/pro/registerServerComponent/server.rsc.ts
  • lib/react_on_rails/pro/helper.rb
  • node_package/src/pro/StoreRegistry.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for all JavaScript/TypeScript code

Files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/CallbackRegistry.ts
node_package/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place NPM package client-side source under node_package/src

Files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/CallbackRegistry.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ClientSideRenderer.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
  • node_package/src/pro/CallbackRegistry.ts
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • node_package/src/pro/getReactServerComponent.client.ts
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • node_package/src/pro/ReactOnRailsRSC.ts
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
PR: shakacode/react_on_rails#1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • node_package/src/pro/ReactOnRailsRSC.ts
🧬 Code graph analysis (1)
node_package/src/pro/ClientSideRenderer.ts (1)
node_package/src/context.ts (1)
  • getRailsContext (14-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: claude-review
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
🔇 Additional comments (11)
node_package/src/pro/CallbackRegistry.ts (2)

1-13: Pro license header looks good.

Header clearly states Pro licensing and links to the Pro license.


15-17: Review comment incorrect — repo permits .ts extension imports

root tsconfig.json has "allowImportingTsExtensions": true and "rewriteRelativeImportExtensions": true, so importing modules with .ts extensions is supported; do not apply the suggested diff.

Likely an incorrect or invalid review comment.

node_package/src/pro/getReactServerComponent.client.ts (2)

17-19: Import path shifts to shared utils/types LGTM.

Paths updated to ../utils.ts and ../types/index.ts align with the new pro/ layout.


1-13: License header verified — link and consistency OK.

REACT-ON-RAILS-PRO-LICENSE.md exists at the repository root and origin's default branch is master (origin/HEAD → origin/master), so the URL referencing .../blob/master/REACT-ON-RAILS-PRO-LICENSE.md is correct; the same header appears across node_package/src/pro/*. Optional: add an SPDX identifier if you use them.

node_package/src/pro/ReactOnRailsRSC.ts (3)

24-27: Import repoints to core modules LGTM.

Switching to ../types, ../ReactOnRails.full.ts, ../handleError.ts, ../serverRenderUtils.ts, and ../loadJsonFile.ts matches the reorg.

Also applies to: 34-34


110-110: Re‑exporting types from ../types/index.ts LGTM.

Keeps public surface stable after the move.


1-13: License header: link and consistency verified.
All node_package/src/pro/* files point to https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md and REACT-ON-RAILS-PRO-LICENSE.md exists at the repository root.

node_package/src/pro/ClientSideRenderer.ts (4)

35-37: Consistent Pro warning string for force-loading.

Message is clear and matches the immediate_hydration warning style.


1-13: Pro license header: file exists and references verified.

REACT-ON-RAILS-PRO-LICENSE.md is present at the repo root and is referenced by files under node_package/src/pro (confirmed).


18-25: Import path rewiring — exports and TS config verified.
isServerRenderHash is exported; reactApis.cts exists; tsconfig.json sets "allowImportingTsExtensions": true; StoreRegistry and ComponentRegistry export the required symbols (setStore, getOrWaitForStore, getOrWaitForStoreGenerator, getOrWaitForComponent).


315-325: Confirm non‑Pro parity for force‑loaded components — non‑Pro ClientSideRenderer not found.

Stores are Pro‑gated; I couldn't find a non‑Pro ClientSideRenderer under node_package/src to verify component gating. Confirm component force‑load and hydration during document.readyState === 'loading' are blocked when getRailsContext()?.rorPro is falsy.

Optional: mirror the store gating for components by adding an rorPro check and warning before forAllElementsAsync (suggested diff):

-export const renderOrHydrateForceLoadedComponents = () =>
-  forAllElementsAsync('.js-react-on-rails-component[data-force-load="true"]', renderOrHydrateComponent);
+export const renderOrHydrateForceLoadedComponents = () => {
+  const hasProLicense = getRailsContext()?.rorPro;
+  if (!hasProLicense) {
+    console.warn(FORCE_LOADING_PRO_WARNING);
+    return Promise.resolve();
+  }
+  return forAllElementsAsync(
+    '.js-react-on-rails-component[data-force-load="true"]',
+    renderOrHydrateComponent,
+  );
+};

@justin808 justin808 changed the title Move RSC features into prop directory and update licence to make them part of RORP licence Move RSC features into pro directory and update licence to make them part of RORP licence Sep 19, 2025
Copy link

claude bot commented Sep 19, 2025

Code Review: Move RSC Features to Pro Directory

Thank you for this PR that restructures the RSC features to be part of the React on Rails Pro license. I've reviewed the changes and here's my comprehensive feedback:

✅ Code Quality and Best Practices

Strengths:

  • Clean separation of Pro features into dedicated pro/ directories
  • Consistent licensing headers across all Pro files
  • Good use of modular design with clear imports/exports
  • Proper CSS escaping with CSS.escape() for dynamic selectors (security best practice)

Suggestions:

  1. The generate_component_script and generate_store_script methods in lib/react_on_rails/pro/helper.rb are quite long. Consider extracting the immediate hydration logic into separate private methods for better readability.

  2. In lib/react_on_rails/pro/helper.rb:71, there's a formatting issue with the conditional:

    store_hydration_scripts =if redux_store_data[:immediate_hydration]  # Missing space after =

    Should be: store_hydration_scripts = if redux_store_data[:immediate_hydration]

🐛 Potential Issues

  1. Badge HTML Generation: The warning badge uses inline styles with high z-index (9999). This could potentially conflict with user's existing UI elements. Consider making this configurable or lowering the z-index.

  2. License Check Logic: The Pro features are silently disabled when license is invalid, but the configuration might still expect them to work. This could lead to confusing behavior. The current approach of showing warnings is good, but consider also logging which features were disabled.

  3. Backwards Compatibility: The removal of badge concatenation in multiple places (e.g., lib/react_on_rails/helper.rb:72,230) leaves code like:

    result[COMPONENT_HTML_KEY] = result[COMPONENT_HTML_KEY]

    This is a no-op and should be removed.

⚡ Performance Considerations

  1. License Validation Caching: The support_pro_features? method calls ReactOnRails::Utils.react_on_rails_pro_licence_valid? repeatedly. Consider caching this result at the request level to avoid redundant checks.

  2. DOM Queries: Good use of CSS.escape() for security, but multiple document.querySelector calls could be optimized by batching or using more specific selectors when possible.

🔒 Security

Positives:

  • Proper escaping of user input with escape_javascript() and CSS.escape()
  • License validation happens server-side, preventing client-side tampering
  • Clear separation of licensed code with appropriate headers

Recommendations:

  • Consider adding rate limiting or caching for license validation checks to prevent potential DoS scenarios
  • The tooltip text in the warning badge should also be HTML-escaped to prevent XSS if it ever includes user-provided content

🧪 Test Coverage

The test coverage looks minimal for these changes:

  • Only one test file for Pro license validation (proLicenseValidation.test.js)
  • Tests only cover the hydrateForceLoadedStores function
  • Missing tests for the Ruby Pro helper module
  • No integration tests for the immediate hydration feature with invalid license

Recommendations:

  1. Add tests for ReactOnRails::Pro::Helper methods
  2. Add tests for ReactOnRails::Pro::Utils.disable_pro_render_options_if_not_licensed
  3. Add integration tests that verify the warning badge appears when Pro features are used without license
  4. Test the edge cases like missing rails context, invalid configuration, etc.

📝 Documentation & PR Checklist

The PR checklist items are unchecked:

  • Tests need to be added/updated
  • Documentation needs updating for the new Pro directory structure
  • CHANGELOG entry is missing

Please complete these items before merging.

Summary

Overall, this is a well-structured refactoring that cleanly separates Pro features. The main concerns are around test coverage, minor code cleanup issues, and ensuring the documentation is updated to reflect these changes. The security considerations have been handled well with proper escaping and server-side validation.

Please address the test coverage and minor issues mentioned above. Happy to review again once these are addressed!

Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791: Move RSC features into pro directory

Thank you for this important refactoring to properly segregate React on Rails Pro features. Here is my comprehensive review:

✅ Strengths

  1. Clear License Separation: The licensing structure is well-organized with MIT for core and a separate Pro license for premium features. The LICENSE.md clearly states which directories contain Pro-licensed code.

  2. Improved Security with CSS.escape(): Great catch on adding CSS.escape() for DOM selectors. This prevents potential XSS vulnerabilities when component IDs contain special characters in multiple locations throughout the codebase.

  3. Clean Module Organization: Moving Pro features to dedicated directories (lib/react_on_rails/pro/ and node_package/src/pro/) improves maintainability and makes the licensing boundaries clear.

  4. Backward Compatibility: The implementation maintains backward compatibility by gracefully degrading when Pro features are used without a license, showing warnings instead of breaking.

⚠️ Issues to Address

  1. Incomplete PR Description: The PR description still contains template text. Please update it with:

    • Clear summary of what is being moved and why
    • Impact on existing users
    • Migration guide if needed
  2. Missing CHANGELOG Entry: Per your PR checklist, the CHANGELOG needs to be updated with this breaking change.

  3. Test Coverage Gaps:

    • The new proLicenseValidation.test.js only tests client-side behavior
    • Missing Ruby specs for the new ReactOnRails::Pro::Helper module
    • No integration tests for the license validation flow
  4. Potential JavaScript Injection: In lib/react_on_rails/pro/helper.rb:44, while you escape the dom_id, consider if other dynamic values need escaping.

🔧 Suggestions for Improvement

  1. Add Migration Documentation: Consider adding a migration guide for users who currently use Pro features, explaining:

    • How to obtain a Pro license
    • What features are affected
    • How to update their code if needed
  2. Enhanced Error Messages: The warning message could be more specific about which exact feature triggered it (immediate_hydration vs other future Pro features).

  3. Package Publishing Strategy: The new package.oss.json excludes Pro files from the OSS package. Ensure the publishing workflow is updated to handle both packages correctly.

  4. Type Safety: Consider adding TypeScript types for the Pro feature flags and license validation to catch issues at compile time.

🚀 Performance Considerations

  • The license validation check appears to run on every Pro feature usage. Consider caching the result to avoid repeated validation checks.
  • The immediate hydration feature now has additional script tags - verify this does not impact initial page load performance.

🔒 Security Review

  • ✅ DOM selector escaping properly implemented
  • ✅ No sensitive license keys exposed in client-side code
  • ✅ Server-side license validation appears secure
  • ⚠️ Ensure the Pro license file path validation prevents directory traversal

📋 Action Items

Before merging:

  1. Update PR description with actual summary
  2. Add CHANGELOG entry
  3. Add Ruby tests for ReactOnRails::Pro::Helper
  4. Add integration tests for license validation
  5. Document migration path for existing Pro users
  6. Verify package publishing workflow handles both OSS and Pro packages

Overall, this is a well-structured refactoring that properly separates licensed features. With the above issues addressed, this will provide a clean foundation for React on Rails Pro features.

Copy link
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: 0

♻️ Duplicate comments (2)
lib/react_on_rails/pro/helper.rb (2)

24-53: Component script generation: escaping fix is correct; align args (RuboCop).

Escaping dom_id in the immediate script resolves the prior XSS concern. Please address Layout/ArgumentAlignment warnings for the content_tag call.

-      component_specification_tag = content_tag(:script,
-        json_safe_and_pretty(render_options.client_props).html_safe,
-        type: "application/json",
-        class: "js-react-on-rails-component",
-        id: "js-react-on-rails-component-#{render_options.dom_id}",
-        "data-component-name" => render_options.react_component_name,
-        "data-trace" => (render_options.trace ? true : nil),
-        "data-dom-id" => render_options.dom_id,
-        "data-store-dependencies" => render_options.store_dependencies&.to_json,
-        "data-immediate-hydration" =>
-          (render_options.immediate_hydration ? true : nil))
+      component_specification_tag = content_tag(
+        :script,
+        json_safe_and_pretty(render_options.client_props).html_safe,
+        type: "application/json",
+        class: "js-react-on-rails-component",
+        id: "js-react-on-rails-component-#{render_options.dom_id}",
+        "data-component-name" => render_options.react_component_name,
+        "data-trace" => (render_options.trace ? true : nil),
+        "data-dom-id" => render_options.dom_id,
+        "data-store-dependencies" => render_options.store_dependencies&.to_json,
+        "data-immediate-hydration" => (render_options.immediate_hydration ? true : nil)
+      )

63-69: Do not mark store_name HTML-safe in attribute (XSS risk).

data-js-react-on-rails-store value should not be .html_safe. Let content_tag escape it.

-        "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe,
+        "data-js-react-on-rails-store" => redux_store_data[:store_name],
🧹 Nitpick comments (12)
lib/react_on_rails/pro/utils.rb (2)

17-19: Adopt nested module style and add final newline (RuboCop).

Switch to nested modules to satisfy Style/ClassAndModuleChildren and add trailing newline.

-module ReactOnRails::Pro
-  module Utils
+module ReactOnRails
+  module Pro
+    module Utils
@@
-  end
-end
+    end
+  end
+end
+

Also applies to: 50-51


27-49: Clarify selection logic for disabled Pro options.

The select with next works but is a bit opaque. Use explicit local and boolean cast; also prefer public_send.

-      explicitly_disabled_pro_options = PRO_ONLY_OPTIONS.select do |option|
-        # Use global configuration if it's not overridden in the options
-        next ReactOnRails.configuration.send(option) if raw_options[option].nil?
-
-        raw_options[option]
-      end
+      explicitly_disabled_pro_options = PRO_ONLY_OPTIONS.select do |option|
+        value = raw_options.key?(option) ? raw_options[option] : ReactOnRails.configuration.public_send(option)
+        !!value
+      end
lib/react_on_rails/pro/helper.rb (6)

17-18: Use nested modules (RuboCop).

Match preferred style: module ReactOnRails; module Pro; module Helper; …

-module ReactOnRails::Pro
-  module Helper
+module ReactOnRails
+  module Pro
+    module Helper
@@
-  end
-end
+    end
+  end
+end

19-22: Remove unused constant.

IMMEDIATE_HYDRATION_PRO_WARNING isn’t referenced. Drop it to avoid dead code.

-    IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \
-                                      "React on Rails Pro license. " \
-                                      "Please visit https://shakacode.com/react-on-rails-pro to learn more."

71-81: Fix spacing/indent and avoid redundant strip_heredoc.

Address RuboCop warnings and simplify heredoc processing.

-      store_hydration_scripts =if redux_store_data[:immediate_hydration]
+      store_hydration_scripts = if redux_store_data[:immediate_hydration]
@@
-        immediate_script = content_tag(:script, <<~JS.strip_heredoc.html_safe
+        immediate_script = content_tag(:script, <<~JS.html_safe
           typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{escaped_store_name}');
         JS
         )

91-95: Prefer logger over puts; fix string concatenation.

Avoid puts in server code and use interpolation/backslash for multiline.

-      warning_message = "[REACT ON RAILS] #{disabled_features_message}" + "\n" +
-                        "Please visit https://shakacode.com/react-on-rails-pro to learn more."
-      puts warning_message
+      warning_message = "[REACT ON RAILS] #{disabled_features_message}\n" \
+                        "Please visit https://shakacode.com/react-on-rails-pro to learn more."
       Rails.logger.warn warning_message

98-107: Return badge string directly; drop redundant assignment.

Simplify and satisfy Style/RedundantAssignment.

-      badge_html = <<~HTML.strip
+      <<~HTML.strip
         <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}">
           <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;">
             <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
               React On Rails Pro Required
             </div>
           </div>
         </a>
       HTML
-      badge_html

110-117: Minor: consistent double quotes (RuboCop).

Switch single to double quotes to satisfy Style/StringLiterals.

-      feature_list = explicitly_disabled_pro_options.join(', ')
+      feature_list = explicitly_disabled_pro_options.join(", ")
lib/react_on_rails/helper.rb (4)

72-72: Redundant html_safe likely harmless.

build_react_component_result_for_server_rendered_string already returns html_safe; this extra call is redundant but OK.


231-231: Remove self-assignment (RuboCop Lint/SelfAssignment).

No-op line; delete it.

-        result[COMPONENT_HTML_KEY] = result[COMPONENT_HTML_KEY]

271-271: Drop redundant parentheses.

Satisfy Style/RedundantParentheses.

-        (prepend_render_rails_context(result)).html_safe
+        prepend_render_rails_context(result).html_safe

658-658: Remove trailing comma (RuboCop).

Tidy hash literal in return value.

       {
         render_options: render_options,
         tag: component_specification_tag,
-        result: result,
+        result: result
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58212db and 153a6ce.

📒 Files selected for processing (4)
  • lib/react_on_rails/helper.rb (6 hunks)
  • lib/react_on_rails/pro/helper.rb (1 hunks)
  • lib/react_on_rails/pro/utils.rb (1 hunks)
  • lib/react_on_rails/react_component/render_options.rb (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Use RuboCop for all Ruby code

Files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/pro/utils.rb
  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
lib/**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Place Ruby gem source under lib/ (e.g., lib/react_on_rails/*)

Files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/pro/utils.rb
  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
🧠 Learnings (10)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/pro/utils.rb
  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/pro/utils.rb
  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • lib/react_on_rails/react_component/render_options.rb
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • lib/react_on_rails/pro/helper.rb
  • lib/react_on_rails/helper.rb
🧬 Code graph analysis (4)
lib/react_on_rails/react_component/render_options.rb (1)
lib/react_on_rails/pro/utils.rb (1)
  • disable_pro_render_options_if_not_licensed (27-49)
lib/react_on_rails/pro/utils.rb (1)
lib/react_on_rails/utils.rb (1)
  • react_on_rails_pro_licence_valid? (216-229)
lib/react_on_rails/pro/helper.rb (3)
lib/react_on_rails/helper.rb (1)
  • json_safe_and_pretty (351-362)
lib/react_on_rails/react_component/render_options.rb (5)
  • client_props (34-46)
  • dom_id (52-60)
  • trace (82-84)
  • store_dependencies (142-144)
  • immediate_hydration (102-104)
lib/react_on_rails/pro/utils.rb (1)
  • disable_pro_render_options_if_not_licensed (27-49)
lib/react_on_rails/helper.rb (2)
lib/react_on_rails/react_component/render_options.rb (1)
  • include (8-175)
lib/react_on_rails/pro/helper.rb (2)
  • generate_component_script (25-54)
  • generate_store_script (58-85)
🪛 GitHub Actions: Lint JS and Ruby
lib/react_on_rails/pro/utils.rb

[warning] 17-17: RuboCop: [Correctable] Style/ClassAndModuleChildren: Use nested module/class definitions instead of compact style.


[warning] 51-51: RuboCop: [Correctable] Layout/TrailingEmptyLines: Final newline missing.

lib/react_on_rails/pro/helper.rb

[warning] 17-17: RuboCop: [Correctable] Style/ClassAndModuleChildren: Use nested module/class definitions instead of compact style.


[warning] 29-29: RuboCop: [Correctable] Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.


[warning] 30-30: RuboCop: [Correctable] Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.


[warning] 43-43: RuboCop: [Correctable] Layout/IndentationWidth: Use 2 (not -9) spaces for indentation.


[warning] 48-48: RuboCop: [Correctable] Layout/ElseAlignment: Align else with if.


[warning] 50-50: RuboCop: [Correctable] Layout/EndAlignment: End alignment is not aligned with if.


[warning] 64-64: RuboCop: [Correctable] Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.


[warning] 65-65: RuboCop: [Correctable] Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.


[warning] 71-71: RuboCop: [Correctable] Layout/SpaceAroundOperators: Surrounding space missing for operator =.


[warning] 73-73: RuboCop: [Correctable] Layout/IndentationWidth: Use 2 (not -23) spaces for indentation.


[warning] 79-79: RuboCop: [Correctable] Layout/ElseAlignment: Align else with if.


[warning] 81-81: RuboCop: [Correctable] Layout/EndAlignment: End alignment is not aligned with if.


[warning] 91-91: RuboCop: [Correctable] Style/StringConcatenation: Prefer string interpolation to string concatenation.


[warning] 91-91: RuboCop: [Correctable] Style/LineEndConcatenation: Use \ instead of + or << to concatenate those strings.


[warning] 98-98: RuboCop: [Correctable] Style/RedundantAssignment: Redundant assignment before returning detected.


[warning] 113-113: RuboCop: [Correctable] Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 116-116: RuboCop: [Correctable] Layout/LineEndStringConcatenationIndentation: Indent the first part of a string concatenated with backslash.

lib/react_on_rails/helper.rb

[warning] 231-231: RuboCop: Self-assignment detected. (Lint/SelfAssignment).


[warning] 271-271: RuboCop: [Correctable] Style/RedundantParentheses: Don't use parentheses around a method call.


[warning] 658-658: RuboCop: [Correctable] Style/TrailingCommaInHashLiteral: Avoid comma after the last item of a hash.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: claude-review
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
🔇 Additional comments (5)
lib/react_on_rails/react_component/render_options.rb (2)

19-21: Centralized Pro‑option gating in initializer looks good.

Options are duplicated and Pro‑only flags disabled when unlicensed; exposing explicitly_disabled_pro_options enables UI hints. Consider adding unit tests for: (a) explicit option true with no license, (b) option nil + global true with no license, (c) licensed path unchanged.

Also applies to: 24-24


4-4: No packaging/require regressions — pro files are included

react_on_rails.gemspec builds s.files with git ls-files and git tracks lib/react_on_rails/pro/helper.rb and lib/react_on_rails/pro/utils.rb, so requiring "react_on_rails/pro/utils" (lib/react_on_rails/react_component/render_options.rb:4) will not cause a LoadError.

lib/react_on_rails/helper.rb (3)

14-20: Clean integration of Pro helper.

Including ReactOnRails::Pro::Helper and delegating to generate_*_script centralizes gating. Nice.


650-650: Delegation to Pro helper for component script LGTM.

Matches new gating flow and removes string‑mutation fragility previously discussed.


663-666: Delegation to Pro helper for store script LGTM.

Ensures license gating and consistent warning badge behavior.

Copy link
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: 0

Caution

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

⚠️ Outside diff range comments (1)
node_package/src/pro/ClientSideRenderer.ts (1)

100-115: Missing Pro gate for hydrating during document “loading” state.

Per prior behavior, pre-page-load hydration is a Pro-only capability. Currently, non-Pro gating happens only when data-immediate-hydration="true". If renderOrHydrateComponent runs while document.readyState === 'loading' without that attribute, non-Pro users can still hydrate early, which violates the intended Pro gating.

Apply this diff to gate both “immediate_hydration” and any pre-load hydration:

-    // Handle immediate_hydration feature usage without Pro license
-    if (isImmediateHydrationRequested && !hasProLicense) {
-      console.warn(IMMEDIATE_HYDRATION_PRO_WARNING);
-
-      // Fallback to standard behavior: wait for page load before hydrating
-      if (document.readyState === 'loading') {
-        await new Promise<void>((resolve) => {
-          onPageLoaded(resolve);
-        });
-      }
-    }
+    // Gate "immediate_hydration" and any pre-load hydration behind Pro
+    const isLoading = document.readyState === 'loading';
+    if (!hasProLicense && (isImmediateHydrationRequested || isLoading)) {
+      if (isImmediateHydrationRequested) warnImmediateHydrationOnce();
+      await new Promise<void>((resolve) => onPageLoaded(resolve));
+    }

Also add a warn-once helper (see comment at Lines 314-327).

🧹 Nitpick comments (4)
node_package/src/pro/ClientSideRenderer.ts (4)

76-78: Guard CSS.escape to avoid runtime/TS support gaps.

CSS.escape isn’t universally typed/available. Use a safe wrapper to prevent runtime errors on older DOM libs/browsers.

Apply this diff:

-        ? document.querySelector(`[data-dom-id="${CSS.escape(domId)}"]`)
+        ? document.querySelector(`[data-dom-id="${escapeForSelector(domId)}"]`)

Add this helper (outside the shown ranges):

// Safer selector escaping for broader DOM/TS support
function escapeForSelector(value: string): string {
  const esc = (globalThis as any).CSS?.escape;
  return typeof esc === 'function' ? esc(value) : value.replace(/["\\]/g, '\\$&');
}

276-280: Naming nit: prefer “ImmediateHydration” over “ImmediateHydrated”.

Consider renderOrHydrateImmediateHydrationComponents for consistency with the feature name.


301-303: Also guard CSS.escape for store selector.

Mirror the earlier safe escape wrapper here.

Apply this diff:

-        ? document.querySelector(`[${REACT_ON_RAILS_STORE_ATTRIBUTE}="${CSS.escape(storeNameOrElement)}"]`)
+        ? document.querySelector(
+            `[${REACT_ON_RAILS_STORE_ATTRIBUTE}="${escapeForSelector(storeNameOrElement)}"]`,
+          )

314-327: Warn only once for non-Pro immediate_hydration store usage.

Avoid console spam if multiple stores/components request immediate hydration without a license. Reuse the same warn-once helper for consistency with the component path.

Apply this diff:

-  if (!hasProLicense) {
-    console.warn(IMMEDIATE_HYDRATION_PRO_WARNING);
-    return Promise.resolve();
-  }
+  if (!hasProLicense) {
+    warnImmediateHydrationOnce();
+    return Promise.resolve();
+  }

Add this helper once (near other constants):

let _warnedImmediateHydration = false;
function warnImmediateHydrationOnce(): void {
  if (_warnedImmediateHydration) return;
  _warnedImmediateHydration = true;
  console.warn(IMMEDIATE_HYDRATION_PRO_WARNING);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 153a6ce and f379c37.

📒 Files selected for processing (3)
  • node_package/src/clientStartup.ts (2 hunks)
  • node_package/src/pro/ClientSideRenderer.ts (5 hunks)
  • node_package/tests/proLicenseValidation.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • node_package/tests/proLicenseValidation.test.js
  • node_package/src/clientStartup.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for all JavaScript/TypeScript code

Files:

  • node_package/src/pro/ClientSideRenderer.ts
node_package/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place NPM package client-side source under node_package/src

Files:

  • node_package/src/pro/ClientSideRenderer.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
🧬 Code graph analysis (1)
node_package/src/pro/ClientSideRenderer.ts (1)
node_package/src/context.ts (1)
  • getRailsContext (14-32)
🔇 Additional comments (3)
node_package/src/pro/ClientSideRenderer.ts (3)

1-13: License header looks correct; verify link and packaging boundaries.

Confirm the LICENSE path is accurate and that npm packaging excludes these Pro files from the MIT bundle (e.g., via files/exports) so OSS consumers don’t inadvertently get Pro code.


18-25: Import path moves LGTM; confirm .cts resolution in your TS/bundler config.

These parent-path imports are consistent with the Pro split. Ensure tsconfig/bundler resolves .cts and mixed ESM/CJS correctly.

Also applies to: 28-28


276-280: Verified — no legacy force_load call sites remain.

Repository search found zero matches for renderOrHydrateForceLoadedComponents, hydrateForceLoadedStores, or data-force-load; only immediate_hydration usages remain (examples: node_package/src/pro/ClientSideRenderer.ts, node_package/src/clientStartup.ts, lib/react_on_rails/pro/helper.rb, lib/react_on_rails/helper.rb, docs/release-notes/16.0.0.md).

Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791: Move RSC features into pro directory

Summary

This PR successfully restructures the codebase to separate pro-licensed features (RSC and immediate hydration) from the open-source core. The implementation is generally solid with notable security improvements, but there are several areas that need attention before merging.

✅ Positive Aspects

  1. Clear License Separation: The dual-licensing structure is well-documented with clear boundaries between MIT and Pro-licensed code.

  2. Security Enhancement: Excellent fix for DOM selector injection vulnerability by properly escaping dynamic IDs using CSS.escape():

    // Before (vulnerable)
    document.querySelector(`[data-dom-id=${domId}]`)
    // After (secure)  
    document.querySelector(`[data-dom-id="${CSS.escape(domId)}"]`)
  3. Consistent Code Organization: Pro features are cleanly organized under /lib/react_on_rails/pro/ and /node_package/src/pro/ directories.

  4. Backward Compatibility: The public API remains unchanged with pro features gracefully degrading when unlicensed.

⚠️ Areas of Concern

1. Missing Test Coverage

  • No tests found for the new ReactOnRails::Pro::Helper module
  • No tests for ReactOnRails::Pro::Utils.disable_pro_render_options_if_not_licensed
  • Missing tests for the CSS.escape security fix
  • No tests validating pro license badge rendering

Recommendation: Add comprehensive test coverage for all new modules, especially license validation logic.

2. Potential Breaking Changes

  • Renaming from force_load to immediate_hydration could break existing implementations
  • The attribute change from data-force-load to data-immediate-hydration needs migration guidance

Recommendation: Document migration path in CHANGELOG and consider supporting both attributes temporarily with deprecation warnings.

3. License Headers Compliance

Several pro files may be missing the required copyright header. Please audit all files in pro directories to ensure consistent license headers.

Recommendation: Verify all files under /lib/react_on_rails/pro/ and /node_package/src/pro/ have proper headers.

4. Performance Considerations

# In generate_component_script and generate_store_script
"#{pro_warning_badge}\n#{spec_tag}".html_safe

The badge HTML is generated on every component/store render, even when not needed.

Recommendation: Cache the badge HTML or generate it once per request.

5. Error Handling

# lib/react_on_rails/pro/utils.rb
explicitly_disabled_pro_options = PRO_ONLY_OPTIONS.select do |option|
  next ReactOnRails.configuration.send(option) if raw_options[option].nil?
  raw_options[option]
end

Using send with dynamic method names could raise NoMethodError if configuration doesn't respond to the method.

Recommendation: Add proper error handling or use respond_to? check.

6. HTML Safety Concern

# lib/react_on_rails/pro/helper.rb
badge_html = <<~HTML.strip
  <a href="https://shakacode.com/react-on-rails-pro" ...>
    ...
  </a>
HTML
badge_html  # Missing .html_safe here, added later in calling code

Recommendation: Apply .html_safe consistently at the source.

📝 Additional Recommendations

  1. Documentation Updates:

    • Update README to clarify dual-licensing model
    • Add migration guide for force_loadimmediate_hydration change
    • Document which features require Pro license
  2. Type Safety:
    Add TypeScript types for new pro modules to maintain type consistency.

  3. Integration Testing:
    Add integration tests that verify pro features are properly disabled without a license.

  4. Configuration Validation:
    Consider adding validation for pro-only configuration options at initialization time.

🔒 Security Notes

The CSS.escape fix is critical and should be highlighted in the changelog as a security improvement. Consider backporting this fix to previous versions if applicable.

Overall Assessment

This is a well-structured refactoring that achieves its goal of separating pro features. However, the lack of test coverage and potential breaking changes need to be addressed before merging. The security improvements are excellent and the code organization is clean.

Status: Changes requested - please address test coverage and migration concerns.

Copy link
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: 0

🧹 Nitpick comments (2)
spec/react_on_rails/react_component/render_options_spec.rb (1)

22-25: LGTM! Stubbing approach aligns with Pro licensing refactor.

The before hook correctly stubs the pro license validation to enable testing of pro features without requiring an actual license. This approach is consistent with the PR's broader refactor that centralizes Pro licensing logic.

The TODO comment appropriately flags the need for comprehensive testing of both licensed and unlicensed scenarios.

Would you like me to help generate tests that cover pro feature behavior both with and without a valid license? This would address the TODO and ensure comprehensive coverage of the licensing logic.

node_package/src/pro/ClientSideRenderer.ts (1)

31-33: Unify Pro license warning wording across the codebase.

Client JS and Ruby messages differ — node_package/src/pro/ClientSideRenderer.ts uses "...to get a license." while lib/react_on_rails/pro/helper.rb (and other Pro warnings) use "...to learn more." Standardize both (recommend: "Please visit https://shakacode.com/react-on-rails-pro to learn more.") and update the JS constant (and any tests asserting the full string) to match.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f379c37 and afaad7d.

📒 Files selected for processing (2)
  • node_package/src/pro/ClientSideRenderer.ts (5 hunks)
  • spec/react_on_rails/react_component/render_options_spec.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

Use RuboCop for all Ruby code

Files:

  • spec/react_on_rails/react_component/render_options_spec.rb
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for all JavaScript/TypeScript code

Files:

  • node_package/src/pro/ClientSideRenderer.ts
node_package/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place NPM package client-side source under node_package/src

Files:

  • node_package/src/pro/ClientSideRenderer.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • spec/react_on_rails/react_component/render_options_spec.rb
  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • spec/react_on_rails/react_component/render_options_spec.rb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • node_package/src/pro/ClientSideRenderer.ts
🔇 Additional comments (8)
node_package/src/pro/ClientSideRenderer.ts (8)

1-13: LGTM! Appropriate Pro license header.

The licensing header correctly indicates this file is part of the React on Rails Pro offering and not covered by the MIT license. This aligns with the PR's goal of moving RSC features into the Pro directory structure.


18-28: LGTM! Correct import path adjustments for Pro structure.

The import paths have been properly updated to reference parent directory modules (using ../) as expected when moving to the pro/ subdirectory. The relative imports maintain the correct module resolution.


76-78: Security improvement with CSS.escape for DOM selection.

Good security enhancement! The CSS.escape() static method returns a string containing the escaped string passed as parameter, mostly for use as part of a CSS selector. This prevents CSS injection attacks when domId contains special characters.

It's been available across browsers since January 2020, so browser compatibility should be good for modern environments.


101-101: Terminology update: immediate-hydration replaces force-load.

The attribute name change from data-force-load to data-immediate-hydration provides clearer semantics. This aligns with the function renaming and makes the feature's purpose more explicit.


276-280: Function renamed with consistent selector update.

The function name change from renderOrHydrateForceLoadedComponents to renderOrHydrateImmediateHydratedComponents correctly matches the new data-immediate-hydration="true" selector. This maintains consistency across the codebase.


302-302: Consistent CSS.escape usage for store selection.

Good application of CSS.escape() for store name selection, maintaining the same security improvement as the DOM ID selection. This ensures consistent escaping across all dynamic selector constructions.


314-315: Store hydration function renamed consistently.

The function hydrateImmediateHydratedStores correctly uses the new data-immediate-hydration="true" selector, maintaining consistency with the component hydration changes.


105-114: Verify Pro license gating logic aligns with previous implementation.

node_package/src/pro/ClientSideRenderer.ts (render method, ~lines 105–114) now only triggers the Pro warning/wait when data-immediate-hydration is true && !railsContext.rorPro. The previous behavior gated both explicit force-load (data-force-load / isComponentForceLoaded) AND document.readyState === 'loading' behind the Pro check. This is a behavior change — confirm it is intentional or restore the previous gating of document.readyState === 'loading'.

Copy link
Contributor

@ihabadham ihabadham left a comment

Choose a reason for hiding this comment

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

LGTM 👍💯

Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791: Move RSC features into pro directory

Thank you for this well-structured refactoring to separate Pro features! I've completed a thorough review of the changes.

Strengths

  • Clean separation of concerns: Moving Pro features to dedicated pro/ directories clearly delineates licensed vs open-source code
  • Consistent licensing headers: All Pro files now have proper copyright notices
  • Graceful feature degradation: Pro features are disabled without breaking functionality when license is invalid
  • User-friendly warnings: The warning badge provides clear visual feedback when Pro features are requested but unavailable
  • Proper XSS protection: Good use of escape_javascript for DOM IDs and store names in JavaScript contexts

🔍 Areas for Improvement

  1. Potential XSS vulnerability in CSS.escape: The client-side selector uses CSS.escape(domId) (line 77 in ClientSideRenderer.ts), but the server-side doesn't validate domId format. Consider validating DOM IDs server-side to ensure they're safe CSS selector values.

  2. Module path consistency: Some imports use relative paths (../context.ts) while the architecture suggests these might benefit from absolute imports for clarity.

  3. Test coverage gaps:

    • No tests for lib/react_on_rails/pro/helper.rb and lib/react_on_rails/pro/utils.rb
    • The generate_component_script and generate_store_script methods handle critical rendering logic but lack direct test coverage
  4. Documentation: The PR description mentions updating documentation as a TODO - this should be completed before merge

🐛 Potential Issues

  1. Badge HTML concatenation: The warning badge is concatenated with raw HTML strings. While currently safe, consider using a more structured approach to prevent future XSS risks.

  2. License check caching: The support_pro_features? method calls react_on_rails_pro_licence_valid? repeatedly. Consider caching this result per request for performance.

💡 Suggestions

  1. Add validation for DOM IDs:
def validate_dom_id(dom_id)
  raise ArgumentError, "Invalid DOM ID" unless dom_id.match?(/\A[a-zA-Z][\w\-]*\z/)
end
  1. Add tests for Pro modules: Create spec/react_on_rails/pro/helper_spec.rb and spec/react_on_rails/pro/utils_spec.rb

  2. Consider using a view component for the warning badge to improve testability and maintainability

  3. Add integration tests that verify Pro features are properly disabled when license is invalid

Security Review

  • Proper use of html_safe only on trusted content
  • JavaScript escaping for user-provided values
  • No exposure of sensitive license validation logic

📊 Performance Considerations

  • The immediate hydration feature should improve perceived performance
  • Consider adding performance metrics/benchmarks for the immediate hydration feature

Overall, this is a solid refactor that achieves the goal of separating Pro features. The main concerns are around test coverage and completing the documentation updates mentioned in the PR checklist.

🤖 Generated with Claude Code

AbanoubGhadban and others added 18 commits September 19, 2025 09:04
- Updated LICENSE.md to clarify the MIT License applicability and introduce the React on Rails Pro License for specific directories.
- Refactored helper methods in `lib/react_on_rails/helper.rb` to utilize new Pro features, including immediate hydration support.
- Introduced `lib/react_on_rails/pro_features/helper.rb` to encapsulate Pro feature logic, including immediate hydration and warning badge generation.
- Adjusted imports in `node_package/src/ReactOnRails.node.ts` to reflect the new Pro directory structure.
- Added new files for React Server Component handling and RSC payload management in the Pro directory, enhancing server-side rendering capabilities.
- Improved error handling and stream management for RSC payloads to optimize performance and debugging.
- Introduced a proprietary license notice to various files in the Pro directory, clarifying that these files are not licensed under the MIT license and are part of React on Rails Pro.
- This update enhances the legal protection of the codebase and ensures compliance with proprietary usage guidelines.
- Moved various React on Rails files to the Pro directory, updating paths in the exports section of package.json.
- This change aligns with the recent restructuring to enhance the organization of Pro features and components.
- Updated import paths in multiple test files to reflect the relocation of components to the Pro directory.
- This change ensures consistency and maintains the integrity of the testing framework following recent structural adjustments.
- Changed import statements in `streamServerRenderedReactComponent.ts` and `client.tsx` to reference the new .cts file extensions for improved module resolution.
- This update aligns with recent changes in the Pro directory structure, ensuring compatibility with the latest file organization.
- Modified the import statement in `registerServerComponent.client.test.jsx` to point to the updated location of `client.tsx` within the Pro directory.
- This change ensures that the test suite remains functional and aligned with recent structural adjustments in the codebase.
- Updated the `helper.rb` file to modify how the `data-immediate-hydration` attribute is added to script tags, ensuring it is correctly applied during immediate hydration.
- Adjusted the test helper spec to correct the formatting of the immediate hydration script, enhancing clarity and functionality.
- Fix XSS security issues in JS string interpolation by adding CSS.escape()
- Add runtime license validation for Pro features (force loading, immediate hydration)
- Implement package.json export controls to prevent Pro module exposure in OSS builds
- Rename pro_features/ to pro/ directory for consistency across Ruby and JS
- Add comprehensive test coverage for Pro license validation
- Update knip configuration for new directory structure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove inappropriate 'confidential' language from Pro feature headers
- Update to proper licensing language for distributed Pro features
- Change from 'Shakacode' to 'Shakacode LLC' for correct legal entity
- Clarify that files are 'licensed separately' not 'confidential'
- Maintain prohibition of unauthorized use while allowing proper licensing

Updated licensing template:
- NOT licensed under MIT (open source) license
- Part of React on Rails Pro offering, licensed separately
- Unauthorized use prohibited without valid license agreement
- Reference to Pro license terms for proper licensing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Removed immediate hydration badge handling from helper methods to streamline rendering logic.
- Introduced `generate_component_script` and `generate_store_script` methods for improved script tag generation, including support for immediate hydration.
- Added `ReactOnRails::Pro::Utils` module to manage Pro feature licensing checks and disable options if the license is not valid.
- Updated `render_options` to incorporate Pro feature validation, ensuring proper handling of disabled options.

This refactor enhances the clarity and maintainability of the Pro features while ensuring compliance with licensing requirements.
- Fix self-assignment issue in main helper
- Apply nested module structure for Pro modules
- Fix indentation and formatting issues
- Use string interpolation instead of concatenation
- Clean up redundant assignment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@justin808 justin808 force-pushed the abanoubghadban/restrict-usage-of-pro-features-to-rorp-licence branch from 46b4226 to 6256ff1 Compare September 19, 2025 19:04
Copy link

claude bot commented Sep 19, 2025

Code Review for PR #1791: Move RSC features into pro directory

Summary

This PR successfully restructures React on Rails to separate Pro features from the open-source core by moving RSC and immediate hydration features to dedicated pro directories and updating the license structure. The implementation is well-organized and includes important security improvements.

✅ Strengths

  1. Clear License Separation: The dual-license structure is well-documented with MIT for core features and a separate license for Pro features in lib/react_on_rails/pro/ and node_package/src/pro/ directories.

  2. Security Enhancement: Great fix for the DOM selector vulnerability using CSS.escape() to properly handle special characters in component/store names (lines 76, 302 in ClientSideRenderer.ts).

  3. Consistent Architecture: The Pro feature isolation follows a clear pattern across both Ruby and JavaScript/TypeScript codebases with proper module organization.

  4. Visual Feedback: The Pro warning badge provides clear user feedback when unlicensed Pro features are attempted, with proper tooltip and styling.

  5. Backward Compatibility: The refactoring maintains API compatibility while gracefully disabling Pro features for unlicensed users.

⚠️ Areas for Improvement

  1. Missing CHANGELOG Entry: The PR checklist indicates CHANGELOG should be updated but no changes were found. This is a breaking change that should be documented.

  2. Incomplete Test Coverage:

    • Tests were only updated to reference new paths, not to verify Pro feature behavior
    • Missing test for the TODO comment: "TODO: test pro features without license"
    • No tests for the Pro warning badge functionality
    • No tests for the explicitly_disabled_pro_options tracking
  3. JavaScript Injection Risk: While escape_javascript is used in helper.rb, consider using a more robust escaping method or parameterized script generation to prevent potential XSS.

  4. Error Handling: The Pro feature detection silently falls back - consider logging at debug level when Pro features are disabled to aid troubleshooting.

  5. Documentation Gap:

    • The PR body template was not properly filled out
    • No migration guide for existing Pro users
    • Missing documentation updates for the new Pro structure

🐛 Potential Issues

  1. Race Condition Risk: The immediate hydration scripts execute synchronously after the component spec tag. Consider using DOMContentLoaded or similar to ensure ReactOnRails is initialized.

  2. Global Namespace: The Pro warning message and badge injection happens in multiple places - consider centralizing this logic to avoid duplication.

  3. Performance: The CSS.escape() calls happen during every component render - consider caching escaped values if the same components are rendered multiple times.

📝 Recommendations

  1. High Priority:

    • Add CHANGELOG entry describing the Pro feature restructuring
    • Add tests for Pro feature gating and warning badge
    • Fill out PR description properly
    • Add migration documentation for existing Pro users
  2. Medium Priority:

    • Consider extracting Pro warning logic to a single module
    • Add debug logging for Pro feature fallbacks
    • Document the new directory structure in README
  3. Nice to Have:

    • Add performance tests for the selector escaping
    • Consider using TypeScript for the Ruby Pro helper module types
    • Add integration tests for Pro license validation flow

✔️ Conclusion

The PR achieves its goal of separating Pro features and improves security with the DOM selector fix. However, it needs test coverage for the Pro feature gating logic and proper documentation updates before merging. The architectural changes are sound and maintain good separation of concerns.

Recommendation: Request changes for missing tests and documentation, then approve.

justin808 and others added 2 commits September 19, 2025 16:42
- Add LICENSE.md files to both Pro directories (Ruby and NPM)
- Files reference root REACT-ON-RAILS-PRO-LICENSE.md for consistency
- Clearly indicate Pro files are omitted from OSS distributions
- Pro directories now have explicit licensing documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update incorrect reference from `lib/react_on_rails/pro_features/`
- to correct `lib/react_on_rails/pro/` directory
- Ensures LICENSE.md accurately reflects actual directory structure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

claude bot commented Sep 20, 2025

Code Review for PR #1791: React on Rails Pro License Separation

Thank you for this comprehensive PR implementing the licensing structure for React on Rails Pro features. I've reviewed the changes and here's my feedback:

✅ Strengths

1. Clean Architecture Separation

  • Excellent separation of Pro features into dedicated lib/react_on_rails/pro/ and node_package/src/pro/ directories
  • Clear license headers on all Pro files
  • Well-structured module organization with ReactOnRails::Pro::Helper and ReactOnRails::Pro::Utils

2. Security Improvements 🔒

  • Great addition of CSS.escape() for DOM selectors to prevent XSS attacks
  • Proper use of escape_javascript() for JavaScript context strings
  • Maintained existing json_safe_and_pretty() for JSON data serialization

3. Backward Compatibility

  • No breaking changes - existing applications will work without modification
  • Graceful fallback when Pro features are used without license
  • Clear warning messages and visual badges for unlicensed Pro feature usage

🔍 Areas for Improvement

1. Error Handling Consistency
The Pro feature detection could benefit from more consistent error handling:

  • Consider adding try-catch blocks around Pro feature invocations
  • Document expected behavior when Pro license validation fails mid-operation

2. Test Coverage
While test files have been updated, consider adding:

  • Explicit tests for Pro license validation logic
  • Tests for the fallback behavior when Pro features are disabled
  • Edge cases around CSS.escape() with special characters

3. Performance Considerations

  • The license validation check (ReactOnRails::Utils.react_on_rails_pro_licence_valid?) is called multiple times - consider caching the result per request
  • The Pro warning badge HTML is generated on every render - could be memoized

🐛 Potential Issues

1. JavaScript String Escaping
In lib/react_on_rails/pro/helper.rb:30-32:

escaped_dom_id = escape_javascript(render_options.dom_id)
immediate_script = content_tag(:script, %(
  typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{escaped_dom_id}');
).html_safe)

Good use of escape_javascript, but ensure dom_id validation prevents injection of control characters.

2. Store Name Validation
Similar concern for store names - while properly escaped, consider adding validation to reject invalid characters at input.

📋 Recommendations

  1. Add Input Validation: Consider adding a whitelist validation for dom_id and store_name to only allow alphanumeric characters, hyphens, and underscores
  2. Performance Optimization: Cache Pro license validation results per request cycle
  3. Documentation: Add migration guide for Pro users in a separate document
  4. Monitoring: Consider adding telemetry for Pro feature usage to help with debugging license issues

✅ Security Review

  • XSS protection improvements are solid
  • No secrets or credentials exposed in the code
  • License validation appears to be server-side only (good)

Summary

This is a well-executed refactoring that cleanly separates Pro features while maintaining security and backward compatibility. The code quality is high, and the architectural decisions are sound. With the minor improvements suggested above, this will be an excellent addition to the codebase.

Verdict: Approved with minor suggestions

Great work on maintaining clean separation of concerns and improving security! 🎉

justin808 and others added 2 commits September 19, 2025 16:52
- Rename lib/react_on_rails/pro/LICENSE.md → NOTICE
- Rename node_package/src/pro/LICENSE.md → NOTICE
- Follows convention for subdirectory licensing notices
- Avoids confusion with main repository LICENSE.md

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Document Core/Pro separation and license architecture changes
- Include Pro feature enhancements and security improvements
- Reference PR 1791 and proper attribution

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@justin808 justin808 merged commit 3e664d9 into master Sep 20, 2025
14 of 15 checks passed
@justin808 justin808 deleted the abanoubghadban/restrict-usage-of-pro-features-to-rorp-licence branch September 20, 2025 03:00
Copy link
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: 0

🧹 Nitpick comments (2)
CHANGELOG.md (2)

28-28: Fix markdownlint MD036: use headings instead of bold-as-heading.

Convert the emphasized pseudo‑headings to real markdown headings to satisfy linters and keep structure consistent.

-**🔐 License Architecture**
+##### 🔐 License Architecture

-**⚡ Pro Feature Enhancements**
+##### ⚡ Pro Feature Enhancements

Also applies to: 35-35


30-34: Call out “no breaking changes/migrations” explicitly.

PR claims no breaking changes; surface this in the Unreleased entry for quick scanning.

 - **Updated LICENSE.md**: Clearly distinguishes core MIT license from Pro-licensed directories
+ - **No breaking changes**: Pro features auto-enable when a valid Pro license is present; no migrations required
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85b7530 and 819e538.

📒 Files selected for processing (3)
  • CHANGELOG.md (1 hunks)
  • lib/react_on_rails/pro/NOTICE (1 hunks)
  • node_package/src/pro/NOTICE (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • node_package/src/pro/NOTICE
  • lib/react_on_rails/pro/NOTICE
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • CHANGELOG.md
🪛 markdownlint-cli2 (0.18.1)
CHANGELOG.md

28-28: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


35-35: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: claude-review
🔇 Additional comments (2)
CHANGELOG.md (2)

26-40: Overall: content LGTM; nice doc of Core vs Pro separation.

Good detail, links, and fallback behavior noted.


37-40: Clarify immediate-hydration gating and add CSS.escape polyfill note.

Add to CHANGELOG.md: both data-immediate-hydration="true" and hydrating during document.readyState === "loading" are Pro-only; without a Pro license these paths fall back to standard hydration. Also update the security note to mention adding a polyfill for CSS.escape() when targeting legacy browsers.

Gating and usage confirmed in node_package/src/pro/ClientSideRenderer.ts and node_package/src/pro/getReactServerComponent.client.ts (CSS.escape used in ClientSideRenderer).

Copy link

claude bot commented Sep 20, 2025

Code Review: Move RSC features into pro directory

Overall Assessment: Grade A-

This PR implements a comprehensive and well-architected licensing structure that cleanly separates Pro features from the core MIT-licensed functionality. The changes demonstrate excellent code quality, thoughtful security improvements, and careful attention to backward compatibility.

Strengths

1. License Structure & Pro Feature Separation

  • Clean dual-license implementation with clear boundaries
  • Excellent module organization (ReactOnRails::Pro::* namespacing)
  • Proper NOTICE files in Pro directories
  • Centralized Pro validation in ReactOnRails::Pro::Utils

2. Security Improvements 🛡️

Excellent hardening against XSS attacks:

  • ✅ CSS.escape() for DOM selectors prevents CSS injection
  • ✅ escape_javascript() for Ruby string interpolation
  • ✅ Proper attribute escaping in dynamic selectors

3. Code Quality

  • Follows Ruby/Rails and JavaScript/TypeScript best practices
  • Consistent coding style and module organization
  • Good separation of concerns
  • Clean abstraction boundaries

4. Backward Compatibility

  • No breaking changes detected
  • Graceful degradation when Pro license unavailable
  • Public APIs remain unchanged
  • Clear warning badges for disabled features

⚠️ Areas for Improvement

1. Security Hardening (High Priority)

Add HTML escaping for tooltip text in warning badges to prevent potential XSS:

# lib/react_on_rails/pro/helper.rb
title="#{html_escape(tooltip_text)}"  # Add html_escape

2. Test Coverage (High Priority)

  • Add specific tests for the new security improvements (CSS.escape usage)
  • Test Pro license validation edge cases
  • Verify graceful degradation scenarios
  • Consider adding integration tests for Pro feature fallback

3. Performance Optimization (Medium Priority)

  • Consider caching license validation results to avoid repeated checks
  • Monitor performance impact of additional abstraction layers
  • Profile the impact on render times with Pro features enabled/disabled

4. Documentation (Medium Priority)

  • Update migration guides to explain Pro feature separation
  • Document the security improvements for user awareness
  • Add examples of Pro feature usage with/without license

🔍 Specific Observations

Ruby Changes

  • lib/react_on_rails/helper.rb: Clean delegation to Pro module ✅
  • lib/react_on_rails/pro/helper.rb: Well-structured Pro functionality ✅
  • lib/react_on_rails/react_component/render_options.rb: Good Pro option handling ✅

JavaScript/TypeScript Changes

  • Consistent renaming: ForceLoaded → ImmediateHydrated (more descriptive) ✅
  • Proper module reorganization in node_package/src/pro/ ✅
  • Security improvements with CSS.escape() throughout ✅

📊 Risk Assessment

  • Security Risk: Low (after implementing suggested HTML escaping)
  • Performance Risk: Low (minimal overhead from Pro checks)
  • Compatibility Risk: Very Low (excellent backward compatibility)
  • Maintenance Risk: Low (clean separation improves maintainability)

Commendations

  • Excellent architectural decision to separate Pro features
  • Thorough security improvements addressing real vulnerabilities
  • Maintaining backward compatibility while making major structural changes
  • Clear and transparent licensing structure

📝 Recommendation

APPROVE with minor changes - This PR represents a significant improvement to the codebase. Once the HTML escaping is added and test coverage is enhanced, this will be an excellent addition to React on Rails.

Great work on this comprehensive refactoring! The Pro feature separation is clean, secure, and well-implemented. 👏


Review generated with assistance from Claude Code

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.

3 participants